From ad7e16c33b3584e453db3cbe0103d13d7f1ab1da Mon Sep 17 00:00:00 2001 From: kharhamel Date: Mon, 1 Mar 2021 17:47:00 +0100 Subject: [PATCH] FEATURE: better implementation of the admin global message --- back/src/Controller/BaseController.ts | 3 +- back/src/Controller/DebugController.ts | 1 - back/src/RoomManager.ts | 38 +---- back/src/Services/AdminApi.ts | 67 --------- back/src/Services/SocketManager.ts | 138 ++++-------------- .../ConsoleGlobalMessageManager.ts | 25 +--- .../src/Administration/UserMessageManager.ts | 31 ++-- front/src/Connexion/AdminMessagesService.ts | 34 +++++ front/src/Connexion/RoomConnection.ts | 21 +-- front/src/Phaser/Game/GameScene.ts | 7 +- messages/protos/messages.proto | 8 + pusher/src/App.ts | 3 + pusher/src/Controller/AdminController.ts | 73 +++++++++ .../src/Controller/AuthenticateController.ts | 1 + pusher/src/Controller/IoSocketController.ts | 20 --- pusher/src/Services/ApiClientRepository.ts | 3 +- pusher/src/Services/SocketManager.ts | 6 +- 17 files changed, 177 insertions(+), 302 deletions(-) create mode 100644 front/src/Connexion/AdminMessagesService.ts create mode 100644 pusher/src/Controller/AdminController.ts diff --git a/back/src/Controller/BaseController.ts b/back/src/Controller/BaseController.ts index 0b744082..93c17ab4 100644 --- a/back/src/Controller/BaseController.ts +++ b/back/src/Controller/BaseController.ts @@ -1,5 +1,4 @@ -import {HttpRequest, HttpResponse} from "uWebSockets.js"; -import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable"; +import {HttpResponse} from "uWebSockets.js"; export class BaseController { diff --git a/back/src/Controller/DebugController.ts b/back/src/Controller/DebugController.ts index a94cc616..509d8b2f 100644 --- a/back/src/Controller/DebugController.ts +++ b/back/src/Controller/DebugController.ts @@ -4,7 +4,6 @@ import {HttpRequest, HttpResponse} from "uWebSockets.js"; import { parse } from 'query-string'; import {App} from "../Server/sifrr.server"; import {socketManager} from "../Services/SocketManager"; -import {ServerWritableStream} from "grpc"; export class DebugController { constructor(private App : App) { diff --git a/back/src/RoomManager.ts b/back/src/RoomManager.ts index b6a82850..4302a141 100644 --- a/back/src/RoomManager.ts +++ b/back/src/RoomManager.ts @@ -2,7 +2,8 @@ import {IRoomManagerServer} from "./Messages/generated/messages_grpc_pb"; import { AdminGlobalMessage, AdminMessage, - AdminPusherToBackMessage, + AdminPusherToBackMessage, + AdminRoomMessage, BanMessage, EmptyMessage, ItemEventMessage, @@ -51,12 +52,8 @@ const roomManager: IRoomManagerServer = { } else { if (message.hasJoinroommessage()) { throw new Error('Cannot call JoinRoomMessage twice!'); - /*} else if (message.hasViewportmessage()) { - socketManager.handleViewport(client, message.getViewportmessage() as ViewportMessage);*/ } else if (message.hasUsermovesmessage()) { socketManager.handleUserMovesMessage(room, user, message.getUsermovesmessage() as UserMovesMessage); - /*} else if (message.hasSetplayerdetailsmessage()) { - socketManager.handleSetPlayerDetails(client, message.getSetplayerdetailsmessage() as SetPlayerDetailsMessage);*/ } else if (message.hasSilentmessage()) { socketManager.handleSilentMessage(room, user, message.getSilentmessage() as SilentMessage); } else if (message.hasItemeventmessage()) { @@ -67,8 +64,6 @@ const roomManager: IRoomManagerServer = { socketManager.emitScreenSharing(room, user, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage); } else if (message.hasPlayglobalmessage()) { socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage); - /*} else if (message.hasReportplayermessage()){ - socketManager.handleReportMessage(client, message.getReportplayermessage() as ReportPlayerMessage);*/ } else if (message.hasQueryjitsijwtmessage()){ socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage); }else if (message.hasSendusermessage()) { @@ -119,10 +114,7 @@ const roomManager: IRoomManagerServer = { socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY()); call.end(); }) - - /*call.on('finish', () => { - debug('listenZone finish'); - })*/ + call.on('close', () => { debug('listenZone connection closed'); socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY()); @@ -150,26 +142,6 @@ const roomManager: IRoomManagerServer = { } else { throw new Error('The first message sent MUST be of type JoinRoomMessage'); } - } else { - /*if (message.hasJoinroommessage()) { - throw new Error('Cannot call JoinRoomMessage twice!'); - } else if (message.hasUsermovesmessage()) { - socketManager.handleUserMovesMessage(room, user, message.getUsermovesmessage() as UserMovesMessage); - } else if (message.hasSilentmessage()) { - socketManager.handleSilentMessage(room, user, message.getSilentmessage() as SilentMessage); - } else if (message.hasItemeventmessage()) { - socketManager.handleItemEvent(room, user, message.getItemeventmessage() as ItemEventMessage); - } else if (message.hasWebrtcsignaltoservermessage()) { - socketManager.emitVideo(room, user, message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage); - } else if (message.hasWebrtcscreensharingsignaltoservermessage()) { - socketManager.emitScreenSharing(room, user, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage); - } else if (message.hasPlayglobalmessage()) { - socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage); - } else if (message.hasQueryjitsijwtmessage()){ - socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage); - } else { - throw new Error('Unhandled message type'); - }*/ } } catch (e) { emitError(call, e); @@ -208,6 +180,10 @@ const roomManager: IRoomManagerServer = { callback(null, new EmptyMessage()); }, + sendAdminMessageToRoom(call: ServerUnaryCall, callback: sendUnaryData): void { + socketManager.sendAdminRoomMessage(call.request.getRoomid(), call.request.getMessage()); + callback(null, new EmptyMessage()); + }, }; export {roomManager}; diff --git a/back/src/Services/AdminApi.ts b/back/src/Services/AdminApi.ts index 3e2dd3e0..ef969a76 100644 --- a/back/src/Services/AdminApi.ts +++ b/back/src/Services/AdminApi.ts @@ -1,6 +1,5 @@ import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable"; import Axios from "axios"; -import {v4} from "uuid"; export interface AdminApiData { organizationSlug: string @@ -21,13 +20,6 @@ export interface CharacterTexture { rights: string } -export interface FetchMemberDataByUuidResponse { - uuid: string; - tags: string[]; - textures: CharacterTexture[]; - messages: unknown[]; -} - class AdminApi { async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise { @@ -52,65 +44,6 @@ class AdminApi { ) return res.data; } - - async fetchMemberDataByUuid(uuid: string): Promise { - if (!ADMIN_API_URL) { - return Promise.reject('No admin backoffice set!'); - } - try { - const res = await Axios.get(ADMIN_API_URL+'/api/membership/'+uuid, - { headers: {"Authorization" : `${ADMIN_API_TOKEN}`} } - ) - return res.data; - } catch (e) { - if (e?.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 uuid "'+uuid+'". Performing an anonymous login instead.'); - return { - uuid: v4(), - tags: [], - textures: [], - messages: [], - } - } else { - throw e; - } - } - } - - async fetchMemberDataByToken(organizationMemberToken: string): Promise { - if (!ADMIN_API_URL) { - return Promise.reject('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, - { headers: {"Authorization" : `${ADMIN_API_TOKEN}`} } - ) - return res.data; - } - - async fetchCheckUserByToken(organizationMemberToken: string): Promise { - if (!ADMIN_API_URL) { - return Promise.reject('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, - { headers: {"Authorization" : `${ADMIN_API_TOKEN}`} } - ) - return res.data; - } - - reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string, reportWorldSlug: string) { - return Axios.post(`${ADMIN_API_URL}/api/report`, { - reportedUserUuid, - reportedUserComment, - reporterUserUuid, - reportWorldSlug, - }, - { - headers: {"Authorization": `${ADMIN_API_TOKEN}`} - }); - } } export const adminApi = new AdminApi(); diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 3d6906ea..907036e1 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -1,5 +1,4 @@ import {GameRoom} from "../Model/GameRoom"; -import {CharacterLayer} from "_Model/Websocket/CharacterLayer"; import { ItemEventMessage, ItemStateMessage, @@ -22,7 +21,11 @@ import { Zone as ProtoZone, BatchToPusherMessage, SubToPusherMessage, - UserJoinedZoneMessage, GroupUpdateZoneMessage, GroupLeftZoneMessage, UserLeftZoneMessage, BanUserMessage + UserJoinedZoneMessage, + GroupUpdateZoneMessage, + GroupLeftZoneMessage, + UserLeftZoneMessage, + BanUserMessage, } from "../Messages/generated/messages_pb"; import {User, UserSocket} from "../Model/User"; import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils"; @@ -51,18 +54,6 @@ import crypto from "crypto"; const debug = Debug('sockermanager'); -interface AdminSocketRoomsList { - [index: string]: number; -} -interface AdminSocketUsersList { - [index: string]: boolean; -} - -export interface AdminSocketData { - rooms: AdminSocketRoomsList, - users: AdminSocketUsersList, -} - function emitZoneMessage(subMessage: SubToPusherMessage, socket: ZoneSocket): void { // TODO: should we batch those every 100ms? const batchMessage = new BatchToPusherMessage(); @@ -83,68 +74,13 @@ export class SocketManager { }); } - /*getAdminSocketDataFor(roomId:string): AdminSocketData { - const data:AdminSocketData = { - rooms: {}, - users: {}, - } - const room = this.rooms.get(roomId); - if (room === undefined) { - return data; - } - const users = room.getUsers(); - data.rooms[roomId] = users.size; - users.forEach(user => { - data.users[user.uuid] = true - }) - return data; - }*/ - public async handleJoinRoom(socket: UserSocket, joinRoomMessage: JoinRoomMessage): Promise<{ room: GameRoom; user: User }> { - /*const positionMessage = joinRoomMessage.getPositionmessage(); - if (positionMessage === undefined) { - // TODO: send error message? - throw new Error('Empty pointMessage found in JoinRoomMessage'); - }*/ - - //const position = ProtobufUtils.toPointInterface(positionMessage); - //const viewport = client.viewport; - - //this.sockets.set(client.userId, client); //todo: should this be at the end of the function? //join new previous room const {room, user} = await this.joinRoom(socket, joinRoomMessage); - //const things = room.setViewport(client, viewport); - const roomJoinedMessage = new RoomJoinedMessage(); roomJoinedMessage.setTagList(joinRoomMessage.getTagList()); - /*for (const thing of things) { - if (thing instanceof User) { - const player: ExSocketInterface|undefined = this.sockets.get(thing.id); - if (player === undefined) { - console.warn('Something went wrong. The World contains a user "'+thing.id+"' but this user does not exist in the sockets list!"); - continue; - } - - const userJoinedMessage = new UserJoinedMessage(); - userJoinedMessage.setUserid(thing.id); - userJoinedMessage.setName(player.name); - userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(player.characterLayers)); - userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(player.position)); - - roomJoinedMessage.addUser(userJoinedMessage); - roomJoinedMessage.setTagList(joinRoomMessage.getTagList()); - } else if (thing instanceof Group) { - const groupUpdateMessage = new GroupUpdateMessage(); - groupUpdateMessage.setGroupid(thing.getId()); - groupUpdateMessage.setPosition(ProtobufUtils.toPointMessage(thing.getPosition())); - - roomJoinedMessage.addGroup(groupUpdateMessage); - } else { - console.error("Unexpected type for Movable returned by setViewport"); - } - }*/ for (const [itemId, item] of room.getItemsState().entries()) { const itemStateMessage = new ItemStateMessage(); @@ -158,8 +94,6 @@ export class SocketManager { const serverToClientMessage = new ServerToClientMessage(); serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage); - - //user.socket.write(serverToClientMessage); console.log('SENDING MESSAGE roomJoinedMessage'); socket.write(serverToClientMessage); @@ -168,13 +102,6 @@ export class SocketManager { user }; - /*const serverToClientMessage = new ServerToClientMessage(); - serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage); - - if (!client.disconnecting) { - client.send(serverToClientMessage.serializeBinary().buffer, true); - }*/ - } handleUserMovesMessage(room: GameRoom, user: User, userMovesMessage: UserMovesMessage) { @@ -693,33 +620,6 @@ export class SocketManager { }, 10000); } - /** - * Merges the characterLayers received from the front (as an array of string) with the custom textures from the back. - */ - static mergeCharacterLayersAndCustomTextures(characterLayers: string[], memberTextures: CharacterTexture[]): CharacterLayer[] { - const characterLayerObjs: CharacterLayer[] = []; - for (const characterLayer of characterLayers) { - if (characterLayer.startsWith('customCharacterTexture')) { - const customCharacterLayerId: number = +characterLayer.substr(22); - for (const memberTexture of memberTextures) { - if (memberTexture.id == customCharacterLayerId) { - characterLayerObjs.push({ - name: characterLayer, - url: memberTexture.url - }) - break; - } - } - } else { - characterLayerObjs.push({ - name: characterLayer, - url: undefined - }) - } - } - return characterLayerObjs; - } - public addZoneListener(call: ZoneSocket, roomId: string, x: number, y: number): void { const room = this.rooms.get(roomId); if (!room) { @@ -773,11 +673,6 @@ export class SocketManager { public async handleJoinAdminRoom(admin: Admin, roomId: string): Promise { const room = await socketManager.getOrCreateRoom(roomId); - // Dispatch groups position to newly connected user - /*world.getGroups().forEach((group: Group) => { - this.emitCreateUpdateGroupEvent(socket, group); - });*/ - room.adminJoin(admin); return room; @@ -807,7 +702,7 @@ export class SocketManager { const sendUserMessage = new SendUserMessage(); sendUserMessage.setMessage(message); - sendUserMessage.setType('ban'); + sendUserMessage.setType('ban'); //todo: is the type correct? const subToPusherMessage = new SubToPusherMessage(); subToPusherMessage.setSendusermessage(sendUserMessage); @@ -843,6 +738,27 @@ export class SocketManager { // Let's close the connection when the user is banned. recipient.socket.end(); } + + + sendAdminRoomMessage(roomId: string, message: string) { + const room = this.rooms.get(roomId); + if (!room) { + //todo: this should cause the http call to return a 500 + console.error("In sendAdminRoomMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?"); + return; + } + + room.getUsers().forEach((recipient) => { + const sendUserMessage = new SendUserMessage(); + sendUserMessage.setMessage(message); + sendUserMessage.setType('message'); + + const clientMessage = new ServerToClientMessage(); + clientMessage.setSendusermessage(sendUserMessage); + + recipient.socket.write(clientMessage); + }); + } } export const socketManager = new SocketManager(); diff --git a/front/src/Administration/ConsoleGlobalMessageManager.ts b/front/src/Administration/ConsoleGlobalMessageManager.ts index 196e9e1c..7800e332 100644 --- a/front/src/Administration/ConsoleGlobalMessageManager.ts +++ b/front/src/Administration/ConsoleGlobalMessageManager.ts @@ -3,6 +3,7 @@ import {UserInputManager} from "../Phaser/UserInput/UserInputManager"; import {RoomConnection} from "../Connexion/RoomConnection"; import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels"; import {ADMIN_URL} from "../Enum/EnvironmentVariable"; +import {AdminMessageEventTypes} from "../Connexion/AdminMessagesService"; export const CLASS_CONSOLE_MESSAGE = 'main-console'; export const INPUT_CONSOLE_MESSAGE = 'input-send-text'; @@ -10,13 +11,16 @@ export const UPLOAD_CONSOLE_MESSAGE = 'input-upload-music'; export const INPUT_TYPE_CONSOLE = 'input-type'; export const VIDEO_QUALITY_SELECT = 'select-video-quality'; -export const AUDIO_TYPE = 'audio'; -export const MESSAGE_TYPE = 'message'; +export const AUDIO_TYPE = AdminMessageEventTypes.audio; +export const MESSAGE_TYPE = AdminMessageEventTypes.admin; interface EventTargetFiles extends EventTarget { files: Array; } +/** + * @deprecated + */ export class ConsoleGlobalMessageManager { private readonly divMainConsole: HTMLDivElement; @@ -372,23 +376,6 @@ export class ConsoleGlobalMessageManager { this.buttonSendMainConsole.classList.remove('active'); } - /*activeSettingConsole(){ - this.activeSetting = true; - if(this.activeMessage){ - this.disabledSettingConsole(); - } - this.active(); - this.divSettingConsole.classList.add('active'); - //this.buttonSettingsMainConsole.classList.add('active'); - } - - disabledSettingConsole(){ - this.activeSetting = false; - this.disabled(); - this.divSettingConsole.classList.remove('active'); - //this.buttonSettingsMainConsole.classList.remove('active'); - }*/ - private getSectionId(id: string) : string { return `section-${id}`; } diff --git a/front/src/Administration/UserMessageManager.ts b/front/src/Administration/UserMessageManager.ts index a20b4729..ec02ac3b 100644 --- a/front/src/Administration/UserMessageManager.ts +++ b/front/src/Administration/UserMessageManager.ts @@ -1,39 +1,29 @@ -import {RoomConnection} from "../Connexion/RoomConnection"; import * as TypeMessages from "./TypeMessage"; -import List = Phaser.Structs.List; -import {UpdatedLocalStreamCallback} from "../WebRtc/MediaManager"; import {Banned} from "./TypeMessage"; +import {adminMessagesService} from "../Connexion/AdminMessagesService"; export interface TypeMessageInterface { showMessage(message: string): void; } -export class UserMessageManager { +class UserMessageManager { typeMessages: Map = new Map(); - receiveBannedMessageListener: Set = new Set(); + receiveBannedMessageListener!: Function; - constructor(private Connection: RoomConnection) { + constructor() { const valueTypeMessageTab = Object.values(TypeMessages); Object.keys(TypeMessages).forEach((value: string, index: number) => { const typeMessageInstance: TypeMessageInterface = (new valueTypeMessageTab[index]() as TypeMessageInterface); this.typeMessages.set(value.toLowerCase(), typeMessageInstance); }); - this.initialise(); - } - initialise() { - //receive signal to show message - this.Connection.receiveUserMessage((type: string, message: string) => { - const typeMessage = this.showMessage(type, message); - - //listener on banned receive message + adminMessagesService.messageStream.subscribe((event) => { + const typeMessage = this.showMessage(event.type, event.text); if(typeMessage instanceof Banned) { - for (const callback of this.receiveBannedMessageListener) { - callback(); - } + this.receiveBannedMessageListener(); } - }); + }) } showMessage(type: string, message: string) { @@ -47,6 +37,7 @@ export class UserMessageManager { } setReceiveBanListener(callback: Function){ - this.receiveBannedMessageListener.add(callback); + this.receiveBannedMessageListener = callback; } -} \ No newline at end of file +} +export const userMessageManager = new UserMessageManager() \ No newline at end of file diff --git a/front/src/Connexion/AdminMessagesService.ts b/front/src/Connexion/AdminMessagesService.ts new file mode 100644 index 00000000..a1e7fc21 --- /dev/null +++ b/front/src/Connexion/AdminMessagesService.ts @@ -0,0 +1,34 @@ +import {Subject} from "rxjs"; +import {SendUserMessage} from "../Messages/generated/messages_pb"; + +export enum AdminMessageEventTypes { + admin = 'message', + audio = 'audio', + ban = 'ban', +} + +interface AdminMessageEvent { + type: AdminMessageEventTypes, + text: string; + //todo add optional properties for other event types +} + +//this class is designed to easily allow communication between the RoomConnection objects (that receive the message) +//and the various objects that may render the message on screen +class AdminMessagesService { + private _messageStream: Subject = new Subject(); + public messageStream = this._messageStream.asObservable(); + + constructor() { + this.messageStream.subscribe((event) => console.log('message', event)) + } + + onSendusermessage(message: SendUserMessage) { + this._messageStream.next({ + type: message.getType() as unknown as AdminMessageEventTypes, + text: message.getMessage(), + }) + } +} + +export const adminMessagesService = new AdminMessagesService(); \ No newline at end of file diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index cebd7606..7d48946b 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -42,6 +42,7 @@ import { WebRtcSignalReceivedMessageInterface, } from "./ConnexionModels"; import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures"; +import {adminMessagesService} from "./AdminMessagesService"; const manualPingDelay = 20000; @@ -140,8 +141,6 @@ export class RoomConnection implements RoomConnection { } else if (message.hasRoomjoinedmessage()) { const roomJoinedMessage = message.getRoomjoinedmessage() as RoomJoinedMessage; - //const users: Array = roomJoinedMessage.getUserList().map(this.toMessageUserJoined.bind(this)); - //const groups: Array = roomJoinedMessage.getGroupList().map(this.toGroupCreatedUpdatedMessage.bind(this)); const items: { [itemId: number] : unknown } = {}; for (const item of roomJoinedMessage.getItemList()) { items[item.getItemid()] = JSON.parse(item.getStatejson()); @@ -150,22 +149,12 @@ export class RoomConnection implements RoomConnection { this.userId = roomJoinedMessage.getCurrentuserid(); this.tags = roomJoinedMessage.getTagList(); - //console.log('Dispatching CONNECT') this.dispatch(EventMessage.CONNECT, { connection: this, room: { - //users, - //groups, items } as RoomJoinedMessageInterface }); - - /*console.log('Dispatching START_ROOM') - this.dispatch(EventMessage.START_ROOM, { - //users, - //groups, - items - });*/ } else if (message.hasErrormessage()) { console.error(EventMessage.MESSAGE_ERROR, message.getErrormessage()?.getMessage()); } else if (message.hasWebrtcsignaltoclientmessage()) { @@ -185,7 +174,7 @@ export class RoomConnection implements RoomConnection { } else if (message.hasSendjitsijwtmessage()) { this.dispatch(EventMessage.START_JITSI_ROOM, message.getSendjitsijwtmessage()); } else if (message.hasSendusermessage()) { - this.dispatch(EventMessage.USER_MESSAGE, message.getSendusermessage()); + adminMessagesService.onSendusermessage(message.getSendusermessage() as SendUserMessage); } else { throw new Error('Unknown message received'); } @@ -539,12 +528,6 @@ export class RoomConnection implements RoomConnection { }); } - public receiveUserMessage(callback: (type: string, message: string) => void) { - return this.onMessage(EventMessage.USER_MESSAGE, (message: SendUserMessage) => { - callback(message.getType(), message.getMessage()); - }); - } - public emitGlobalMessage(message: PlayGlobalMessageInterface){ const playGlobalMessage = new PlayGlobalMessage(); playGlobalMessage.setId(message.id); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index a90cb6b1..cc3378a6 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -57,7 +57,7 @@ import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils"; import {connectionManager} from "../../Connexion/ConnectionManager"; import {RoomConnection} from "../../Connexion/RoomConnection"; import {GlobalMessageManager} from "../../Administration/GlobalMessageManager"; -import {UserMessageManager} from "../../Administration/UserMessageManager"; +import {userMessageManager} from "../../Administration/UserMessageManager"; import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMessageManager"; import {ResizableScene} from "../Login/ResizableScene"; import {Room} from "../../Connexion/Room"; @@ -72,7 +72,6 @@ import {TextureError} from "../../Exception/TextureError"; import {addLoader} from "../Components/Loader"; import {ErrorSceneName} from "../Reconnecting/ErrorScene"; import {localUserStore} from "../../Connexion/LocalUserStore"; -import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures"; export interface GameSceneInitInterface { initPosition: PointInterface|null, @@ -131,7 +130,6 @@ export class GameScene extends ResizableScene implements CenterListener { public connection!: RoomConnection; private simplePeer!: SimplePeer; private GlobalMessageManager!: GlobalMessageManager; - private UserMessageManager!: UserMessageManager; public ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager; private connectionAnswerPromise: Promise; private connectionAnswerPromiseResolve!: (value?: RoomJoinedMessageInterface | PromiseLike) => void; @@ -537,8 +535,7 @@ export class GameScene extends ResizableScene implements CenterListener { // When connection is performed, let's connect SimplePeer this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.playerName); this.GlobalMessageManager = new GlobalMessageManager(this.connection); - this.UserMessageManager = new UserMessageManager(this.connection); - this.UserMessageManager.setReceiveBanListener(this.bannedUser.bind(this)); + userMessageManager.setReceiveBanListener(this.bannedUser.bind(this)); const self = this; this.simplePeer.registerPeerConnectionListener({ diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index 54b425f9..36247aef 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -217,6 +217,7 @@ message ServerToClientMessage { SendJitsiJwtMessage sendJitsiJwtMessage = 11; SendUserMessage sendUserMessage = 12; BanUserMessage banUserMessage = 13; + AdminRoomMessage adminRoomMessage = 14; } } @@ -351,6 +352,12 @@ message AdminMessage { string roomId = 3; } +// A message sent by an administrator to everyone in a specific room +message AdminRoomMessage { + string message = 1; + string roomId = 2; +} + // A message sent by an administrator to absolutely everybody message AdminGlobalMessage { string message = 1; @@ -372,4 +379,5 @@ service RoomManager { rpc sendAdminMessage(AdminMessage) returns (EmptyMessage); rpc sendGlobalAdminMessage(AdminGlobalMessage) returns (EmptyMessage); rpc ban(BanMessage) returns (EmptyMessage); + rpc sendAdminMessageToRoom(AdminRoomMessage) returns (EmptyMessage); } diff --git a/pusher/src/App.ts b/pusher/src/App.ts index 49786d3f..7a272404 100644 --- a/pusher/src/App.ts +++ b/pusher/src/App.ts @@ -5,6 +5,7 @@ import {MapController} from "./Controller/MapController"; import {PrometheusController} from "./Controller/PrometheusController"; import {DebugController} from "./Controller/DebugController"; import {App as uwsApp} from "./Server/sifrr.server"; +import {AdminController} from "./Controller/AdminController"; class App { public app: uwsApp; @@ -13,6 +14,7 @@ class App { public mapController: MapController; public prometheusController: PrometheusController; private debugController: DebugController; + private adminController: AdminController; constructor() { this.app = new uwsApp(); @@ -23,6 +25,7 @@ class App { this.mapController = new MapController(this.app); this.prometheusController = new PrometheusController(this.app); this.debugController = new DebugController(this.app); + this.adminController = new AdminController(this.app); } } diff --git a/pusher/src/Controller/AdminController.ts b/pusher/src/Controller/AdminController.ts new file mode 100644 index 00000000..8c4f524d --- /dev/null +++ b/pusher/src/Controller/AdminController.ts @@ -0,0 +1,73 @@ +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} from "../Messages/generated/messages_pb"; + + +export class AdminController extends BaseController{ + + constructor(private App : TemplatedApp) { + super(); + this.App = App; + this.receiveGlobalMessagePrompt(); + } + + receiveGlobalMessagePrompt() { + this.App.options("/message", (res: HttpResponse, req: HttpRequest) => { + this.addCorsHeaders(res); + res.end(); + }); + + // eslint-disable-next-line @typescript-eslint/no-misused-promises + this.App.post("/message", 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.text !== 'string') { + throw 'Incorrect text parameter' + } + if (!body.targets || typeof body.targets !== 'object') { + throw 'Incorrect targets parameter' + } + const text: string = body.text; + const targets: string[] = body.targets; + + await Promise.all(targets.map((roomId) => { + return apiClientRepository.getClient(roomId).then((roomClient) =>{ + return new Promise((res, rej) => { + const roomMessage = new AdminRoomMessage(); + roomMessage.setMessage(text); + roomMessage.setRoomid(roomId); + + roomClient.sendAdminMessageToRoom(roomMessage, (err) => { + err ? rej(err) : res(); + }); + }); + }); + })); + + } catch (err) { + this.errorToResponse(err, res); + return; + } + + res.writeStatus("200"); + this.addCorsHeaders(res); + res.end('ok'); + }); + } +} diff --git a/pusher/src/Controller/AuthenticateController.ts b/pusher/src/Controller/AuthenticateController.ts index f82974b6..a5aa47cc 100644 --- a/pusher/src/Controller/AuthenticateController.ts +++ b/pusher/src/Controller/AuthenticateController.ts @@ -56,6 +56,7 @@ export class AuthenticateController extends BaseController { worldSlug, roomSlug, mapUrlStart, + organizationMemberToken, textures })); diff --git a/pusher/src/Controller/IoSocketController.ts b/pusher/src/Controller/IoSocketController.ts index 6bdbd36d..2391aaa3 100644 --- a/pusher/src/Controller/IoSocketController.ts +++ b/pusher/src/Controller/IoSocketController.ts @@ -21,7 +21,6 @@ import {jwtTokenManager} from "../Services/JWTTokenManager"; import {adminApi, CharacterTexture, FetchMemberDataByUuidResponse} from "../Services/AdminApi"; import {SocketManager, socketManager} from "../Services/SocketManager"; import {emitInBatch} from "../Services/IoSocketHelpers"; -import {clientEventsEmitter} from "../Services/ClientEventsEmitter"; import {ADMIN_API_TOKEN, ADMIN_API_URL, SOCKET_IDLE_TIMER} from "../Enum/EnvironmentVariable"; import {Zone} from "_Model/Zone"; import {ExAdminSocketInterface} from "_Model/Websocket/ExAdminSocketInterface"; @@ -65,22 +64,6 @@ export class IoSocketController { ws.disconnecting = false; socketManager.handleAdminRoom(ws as ExAdminSocketInterface, ws.roomId as string); - - /*ws.send('Data:'+JSON.stringify(socketManager.getAdminSocketDataFor(ws.roomId as string))); - ws.clientJoinCallback = (clientUUid: string, roomId: string) => { - const wsroomId = ws.roomId as string; - if(wsroomId === roomId) { - ws.send('MemberJoin:'+clientUUid+';'+roomId); - } - }; - ws.clientLeaveCallback = (clientUUid: string, roomId: string) => { - const wsroomId = ws.roomId as string; - if(wsroomId === roomId) { - ws.send('MemberLeave:'+clientUUid+';'+roomId); - } - }; - clientEventsEmitter.registerToClientJoin(ws.clientJoinCallback); - clientEventsEmitter.registerToClientLeave(ws.clientLeaveCallback);*/ }, message: (ws, arrayBuffer, isBinary): void => { try { @@ -107,7 +90,6 @@ export class IoSocketController { const Client = (ws as ExAdminSocketInterface); try { Client.disconnecting = true; - //leave room socketManager.leaveAdminRoom(Client); } catch (e) { console.error('An error occurred on admin "disconnect"'); @@ -207,8 +189,6 @@ export class IoSocketController { if (!room.anonymous && room.policyType === GameRoomPolicyTypes.MEMBERS_ONLY_POLICY && userData.anonymous === true) { throw new Error('No correct member') } - - //console.log('access granted for user '+userUuid+' and room '+roomId); } catch (e) { console.log('access not granted for user '+userUuid+' and room '+roomId); console.error(e); diff --git a/pusher/src/Services/ApiClientRepository.ts b/pusher/src/Services/ApiClientRepository.ts index 251b123a..c1c6bd38 100644 --- a/pusher/src/Services/ApiClientRepository.ts +++ b/pusher/src/Services/ApiClientRepository.ts @@ -13,8 +13,7 @@ const debug = Debug('apiClientRespository'); class ApiClientRepository { private roomManagerClients: RoomManagerClient[] = []; - public constructor(private apiUrls: string[]) { - } + public constructor(private apiUrls: string[]) {} public async getClient(roomId: string): Promise { const array = new Uint32Array(crypto.createHash('md5').update(roomId).digest()); diff --git a/pusher/src/Services/SocketManager.ts b/pusher/src/Services/SocketManager.ts index 713b8c7d..5c1f25ed 100644 --- a/pusher/src/Services/SocketManager.ts +++ b/pusher/src/Services/SocketManager.ts @@ -493,11 +493,7 @@ export class SocketManager implements ZoneEventListener { public getWorlds(): Map { return this.Worlds; } - - /** - * - * @param token - */ + searchClientByUuid(uuid: string): ExSocketInterface | null { for(const socket of this.sockets.values()){ if(socket.userUuid === uuid){