diff --git a/back/src/Controller/AuthenticateController.ts b/back/src/Controller/AuthenticateController.ts index 16919b22..db7cfb9f 100644 --- a/back/src/Controller/AuthenticateController.ts +++ b/back/src/Controller/AuthenticateController.ts @@ -40,7 +40,6 @@ export class AuthenticateController extends BaseController { try { if (typeof organizationMemberToken != 'string') throw new Error('No organization token'); const data = await adminApi.fetchMemberDataByToken(organizationMemberToken); - const userUuid = data.userUuid; const organizationSlug = data.organizationSlug; const worldSlug = data.worldSlug; @@ -60,7 +59,7 @@ export class AuthenticateController extends BaseController { })); } catch (e) { - console.log("An error happened", e) + console.error("An error happened", e) res.writeStatus(e.status || "500 Internal Server Error"); this.addCorsHeaders(res); res.end('An error happened'); diff --git a/back/src/Controller/BaseController.ts b/back/src/Controller/BaseController.ts index 93c17ab4..0b744082 100644 --- a/back/src/Controller/BaseController.ts +++ b/back/src/Controller/BaseController.ts @@ -1,4 +1,5 @@ -import {HttpResponse} from "uWebSockets.js"; +import {HttpRequest, HttpResponse} from "uWebSockets.js"; +import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable"; export class BaseController { diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 44addb88..2172ff38 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -1,5 +1,5 @@ import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.." -import {MINIMUM_DISTANCE, GROUP_RADIUS} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..." +import {MINIMUM_DISTANCE, GROUP_RADIUS, ADMIN_API_URL, ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..." import {GameRoom, GameRoomPolicyTypes} from "../Model/GameRoom"; import {Group} from "../Model/Group"; import {User} from "../Model/User"; @@ -31,6 +31,7 @@ import { WebRtcStartMessage, WebRtcDisconnectMessage, PlayGlobalMessage, + ReportPlayerMessage } from "../Messages/generated/messages_pb"; import {UserMovesMessage} from "../Messages/generated/messages_pb"; import Direction = PositionMessage.Direction; @@ -41,6 +42,7 @@ import {cpuTracker} from "../Services/CpuTracker"; import {ViewportInterface} from "../Model/Websocket/ViewportMessage"; import {jwtTokenManager} from "../Services/JWTTokenManager"; import {adminApi} from "../Services/AdminApi"; +import Axios from "axios"; import {PositionInterface} from "../Model/PositionInterface"; function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void { @@ -270,11 +272,13 @@ export class IoSocketController { } else if (message.hasItemeventmessage()) { this.handleItemEvent(client, message.getItemeventmessage() as ItemEventMessage); } else if (message.hasWebrtcsignaltoservermessage()) { - this.emitVideo(client, message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage) + this.emitVideo(client, message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage); } else if (message.hasWebrtcscreensharingsignaltoservermessage()) { - this.emitScreenSharing(client, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage) + this.emitScreenSharing(client, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage); } else if (message.hasPlayglobalmessage()) { - this.emitPlayGlobalMessage(client, message.getPlayglobalmessage() as PlayGlobalMessage) + this.emitPlayGlobalMessage(client, message.getPlayglobalmessage() as PlayGlobalMessage); + } else if (message.hasReportplayermessage()){ + this.handleReportMessage(client, message.getReportplayermessage() as ReportPlayerMessage); } /* Ok is false if backpressure was built up, wait for drain */ @@ -509,6 +513,29 @@ export class IoSocketController { } } + private 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 + Axios.post(`${ADMIN_API_URL}/api/report`, { + reportedUserUuid: reportedSocket.userUuid, + reportedUserComment: reportPlayerMessage.getReportcomment(), + reporterUserUuid: client.userUuid + }, + { + headers: {"Authorization": `${ADMIN_API_TOKEN}`} + }).catch((err) => { + throw err; + }); + } catch (e) { + console.error('An error occurred on "handleReportMessage"'); + console.error(e); + } + } + emitVideo(socket: ExSocketInterface, data: WebRtcSignalToServerMessage): void { //send only at user const client = this.sockets.get(data.getReceiverid()); @@ -870,4 +897,17 @@ export class IoSocketController { public getWorlds(): Map { return this.Worlds; } + + /** + * + * @param token + */ + searchClientByUuid(uuid: string): ExSocketInterface | null { + for(const socket of this.sockets.values()){ + if(socket.userUuid === uuid){ + return socket; + } + } + return null; + } } diff --git a/back/src/Enum/EnvironmentVariable.ts b/back/src/Enum/EnvironmentVariable.ts index b69ba00c..61ab4cc9 100644 --- a/back/src/Enum/EnvironmentVariable.ts +++ b/back/src/Enum/EnvironmentVariable.ts @@ -3,8 +3,8 @@ const URL_ROOM_STARTED = "/Floor0/floor0.json"; const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIMUM_DISTANCE) : 64; const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48; const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == 'true' : false; -const ADMIN_API_URL = process.env.ADMIN_API_URL || null; -const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || null; +const ADMIN_API_URL = process.env.ADMIN_API_URL || 'http://admin'; +const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || 'myapitoken'; const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80; export { diff --git a/front/dist/resources/logos/close.svg b/front/dist/resources/logos/close.svg new file mode 100644 index 00000000..6acd8b49 --- /dev/null +++ b/front/dist/resources/logos/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/dist/resources/logos/report.svg b/front/dist/resources/logos/report.svg new file mode 100644 index 00000000..1cb3b068 --- /dev/null +++ b/front/dist/resources/logos/report.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/dist/resources/objects/teleportation.png b/front/dist/resources/objects/teleportation.png new file mode 100644 index 00000000..e13826f9 Binary files /dev/null and b/front/dist/resources/objects/teleportation.png differ diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index e22795a9..fa91d1e7 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -56,6 +56,12 @@ body .message-info.warning{ padding: 10px; z-index: 2; } + +.video-container img.report{ + right: 5px; + left: auto; +} + .video-container video{ height: 100%; } @@ -567,4 +573,85 @@ body { .main-container .audio-playing p{ color: white; margin-left: 10px; -} \ No newline at end of file +} + +/*REPORT input*/ +div.modal-report-user{ + position: absolute; + width: 800px; + height: 600px; + left: calc(50% - 400px); + top: 100px; + background-color: #000000ad; +} + +.modal-report-user textarea{ + position: absolute; + height: 200px; + z-index: 999; + top: 200px; + background-color: #000000; + color: white; + width: calc(100% - 60px); + margin: 30px; +} + +.modal-report-user img{ + position: absolute; + height: 50px; + width: 50px; + z-index: 999; + left: calc(50% - 25px); + top: 10px; +} + +.modal-report-user img#cancel-report-user{ + position: absolute; + z-index: 999; + right: 0; + left: auto; + top: 0; + cursor: pointer; + width: 15px; + height: 15px; + margin: 10px; +} + +.modal-report-user button{ + position: absolute; + top: 450px; + left: calc(50% - 50px); + width: 100px; + border: 1px solid black; + background-color: #00000000; + color: #ffda01; + border-radius: 10px; + padding: 10px 30px; + transition: all .2s ease; +} +.modal-report-user button:hover{ + cursor: pointer; + background-color: #ffda01; + color: black; + border: 1px solid black; + transform: scale(1.1); +} + +.modal-report-user p#title-report-user{ + font-size: 30px; + color: white; + position: absolute; + top: 30px; + width: 100%; + text-align: center; +} + +.modal-report-user p#body-report-user{ + font-size: 24px; + color: white; + position: absolute; + top: 70px; + width: 100%; + text-align: left; + padding: 30px; +} diff --git a/front/src/Administration/GlobalMessageManager.ts b/front/src/Administration/GlobalMessageManager.ts index 963db8cc..e3b2b503 100644 --- a/front/src/Administration/GlobalMessageManager.ts +++ b/front/src/Administration/GlobalMessageManager.ts @@ -20,6 +20,12 @@ export class GlobalMessageManager { this.Connection.receiveStopGlobalMessage((messageId: string) => { this.stopMessage(messageId); }); + + //receive signal to close message + this.Connection.receiveTeleportMessage((map: string) => { + console.log('map to teleport user', map); + //TODO teleport user on map + }); } private playMessage(message : PlayGlobalMessageInterface){ diff --git a/front/src/Connexion/ConnexionModels.ts b/front/src/Connexion/ConnexionModels.ts index 3df32331..c564ed90 100644 --- a/front/src/Connexion/ConnexionModels.ts +++ b/front/src/Connexion/ConnexionModels.ts @@ -25,6 +25,8 @@ export enum EventMessage{ PLAY_GLOBAL_MESSAGE = "play-global-message", STOP_GLOBAL_MESSAGE = "stop-global-message", + + TELEPORT = "teleport", } export interface PointInterface { diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index e53bc2f8..a9b830d3 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -20,7 +20,9 @@ import { WebRtcDisconnectMessage, WebRtcSignalToClientMessage, WebRtcSignalToServerMessage, - WebRtcStartMessage + WebRtcStartMessage, + ReportPlayerMessage, + TeleportMessageMessage } from "../Messages/generated/messages_pb" import {UserSimplePeerInterface} from "../WebRtc/SimplePeer"; @@ -146,6 +148,8 @@ export class RoomConnection implements RoomConnection { this.dispatch(EventMessage.PLAY_GLOBAL_MESSAGE, message.getPlayglobalmessage()); } else if (message.hasStopglobalmessage()) { this.dispatch(EventMessage.STOP_GLOBAL_MESSAGE, message.getStopglobalmessage()); + } else if (message.hasTeleportmessagemessage()) { + this.dispatch(EventMessage.TELEPORT, message.getTeleportmessagemessage()); } else { throw new Error('Unknown message received'); } @@ -403,7 +407,6 @@ export class RoomConnection implements RoomConnection { } callback(event); }); - } public getUserId(): number|null { @@ -468,6 +471,12 @@ export class RoomConnection implements RoomConnection { }); } + public receiveTeleportMessage(callback: (messageId: string) => void) { + return this.onMessage(EventMessage.TELEPORT, (message: TeleportMessageMessage) => { + callback(message.getMap()); + }); + } + public emitGlobalMessage(message: PlayGlobalMessageInterface){ console.log('emitGlobalMessage', message); const playGlobalMessage = new PlayGlobalMessage(); @@ -481,6 +490,17 @@ export class RoomConnection implements RoomConnection { this.socket.send(clientToServerMessage.serializeBinary().buffer); } + public emitReportPlayerMessage(reportedUserId: number, reportComment: string ): void { + const reportPlayerMessage = new ReportPlayerMessage(); + reportPlayerMessage.setReporteduserid(reportedUserId); + reportPlayerMessage.setReportcomment(reportComment); + + const clientToServerMessage = new ClientToServerMessage(); + clientToServerMessage.setReportplayermessage(reportPlayerMessage); + + this.socket.send(clientToServerMessage.serializeBinary().buffer); + } + public hasTag(tag: string): boolean { return this.tags.includes(tag); } diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index cb526c4a..6f6f769e 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -46,6 +46,7 @@ export abstract class Character extends Container { public PlayerValue: string; public sprites: Map; private lastDirection: string = PlayerAnimationNames.WalkDown; + //private teleportation: Sprite; constructor(scene: Phaser.Scene, x: number, @@ -62,6 +63,7 @@ export abstract class Character extends Container { for (const texture of textures) { const sprite = new Sprite(scene, 0, 0, texture, frame); + sprite.setInteractive({useHandCursor: true}); this.add(sprite); this.getPlayerAnimations(texture).forEach(d => { this.scene.anims.create({ @@ -76,6 +78,15 @@ export abstract class Character extends Container { this.sprites.set(texture, sprite); } + /*this.teleportation = new Sprite(scene, -20, -10, 'teleportation', 3); + this.teleportation.setInteractive(); + this.teleportation.visible = false; + this.teleportation.on('pointerup', () => { + this.report.visible = false; + this.teleportation.visible = false; + }); + this.add(this.teleportation);*/ + this.PlayerValue = name; this.playerName = new BitmapText(scene, x, y - 25, 'main_font', name, 7); this.playerName.setOrigin(0.5).setCenterAlign().setDepth(99999); diff --git a/front/src/Phaser/Entity/RemotePlayer.ts b/front/src/Phaser/Entity/RemotePlayer.ts index ba0a74d2..08f657d4 100644 --- a/front/src/Phaser/Entity/RemotePlayer.ts +++ b/front/src/Phaser/Entity/RemotePlayer.ts @@ -1,6 +1,7 @@ import {GameScene} from "../Game/GameScene"; import {PointInterface} from "../../Connexion/ConnexionModels"; import {Character} from "../Entity/Character"; +import {Sprite} from "./Sprite"; /** * Class representing the sprite of a remote player (a player that plays on another computer) @@ -22,9 +23,6 @@ export class RemotePlayer extends Character { //set data this.userId = userId; - - //the current player model should be push away by other players to prevent conflict - //this.setImmovable(false); } updatePosition(position: PointInterface): void { diff --git a/front/src/Phaser/Entity/body_character.ts b/front/src/Phaser/Entity/body_character.ts index 3d9d5a5f..6fbeaadb 100644 --- a/front/src/Phaser/Entity/body_character.ts +++ b/front/src/Phaser/Entity/body_character.ts @@ -1,4 +1,5 @@ import LoaderPlugin = Phaser.Loader.LoaderPlugin; +import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "./Character"; export interface BodyResourceDescriptionInterface { name: string, @@ -310,3 +311,28 @@ export const loadAllLayers = (load: LoaderPlugin) => { } } } + +export const OBJECTS: Array = [ + {name:'layout_modes', img:'resources/objects/layout_modes.png'}, + {name:'teleportation', img:'resources/objects/teleportation.png'}, +]; + +export const loadObject = (load: LoaderPlugin) => { + for (let j = 0; j < OBJECTS.length; j++) { + load.spritesheet( + OBJECTS[j].name, + OBJECTS[j].img, + {frameWidth: 32, frameHeight: 32} + ) + } +} + +export const loadPlayerCharacters = (load: LoaderPlugin) => { + PLAYER_RESOURCES.forEach((playerResource: PlayerResourceDescriptionInterface) => { + load.spritesheet( + playerResource.name, + playerResource.img, + {frameWidth: 32, frameHeight: 32} + ); + }); +} diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index a2f90b06..8884cdcc 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -25,7 +25,7 @@ import {RemotePlayer} from "../Entity/RemotePlayer"; import {Queue} from 'queue-typescript'; import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer"; import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; -import {loadAllLayers} from "../Entity/body_character"; +import {loadAllLayers, loadObject, loadPlayerCharacters} from "../Entity/body_character"; import {CenterListener, layoutManager, LayoutMode} from "../../WebRtc/LayoutManager"; import Texture = Phaser.Textures.Texture; import Sprite = Phaser.GameObjects.Sprite; @@ -187,21 +187,9 @@ export class GameScene extends ResizableScene implements CenterListener { } //add player png - PLAYER_RESOURCES.forEach((playerResource: PlayerResourceDescriptionInterface) => { - this.load.spritesheet( - playerResource.name, - playerResource.img, - {frameWidth: 32, frameHeight: 32} - ); - }); - - this.load.spritesheet( - 'layout_modes', - 'resources/objects/layout_modes.png', - {frameWidth: 32, frameHeight: 32} - ); - + loadPlayerCharacters(this.load); loadAllLayers(this.load); + loadObject(this.load); this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); } @@ -327,7 +315,7 @@ export class GameScene extends ResizableScene implements CenterListener { }); //permit to set bound collision - this.physics.world.setBounds(0,0, this.Map.widthInPixels, this.Map.heightInPixels); + this.physics.world.setBounds(0, 0, this.Map.widthInPixels, this.Map.heightInPixels); //add layer on map this.Layers = new Array(); @@ -391,7 +379,7 @@ export class GameScene extends ResizableScene implements CenterListener { this.EventToClickOnTile(); //initialise list of other player - this.MapPlayers = this.physics.add.group({ immovable: true }); + this.MapPlayers = this.physics.add.group({immovable: true}); //create input to move this.userInputManager = new UserInputManager(this); @@ -404,7 +392,7 @@ export class GameScene extends ResizableScene implements CenterListener { // Let's generate the circle for the group delimiter const circleElement = Object.values(this.textures.list).find((object: Texture) => object.key === 'circleSprite'); - if(circleElement) { + if (circleElement) { this.textures.remove('circleSprite'); } this.circleTexture = this.textures.createCanvas('circleSprite', 96, 96); @@ -436,7 +424,7 @@ export class GameScene extends ResizableScene implements CenterListener { this.createPromiseResolve(); - this.userInputManager.spaceEvent( () => { + this.userInputManager.spaceEvent(() => { this.outlinedItem?.activate(); }); @@ -526,7 +514,7 @@ export class GameScene extends ResizableScene implements CenterListener { top: camera.scrollY, right: camera.scrollX + camera.width, bottom: camera.scrollY + camera.height, - }).then((connection : RoomConnection) => { + }).then((connection: RoomConnection) => { this.connection = connection; //this.connection.emitPlayerDetailsMessage(gameManager.getPlayerName(), gameManager.getCharacterSelected()) @@ -586,8 +574,8 @@ export class GameScene extends ResizableScene implements CenterListener { this.simplePeer.closeAllConnections(); this.simplePeer.unregister(); - const gameSceneKey = 'somekey'+Math.round(Math.random()*10000); - const game : Phaser.Scene = GameScene.createFromUrl(this.room, this.MapUrlFile, gameSceneKey); + const gameSceneKey = 'somekey' + Math.round(Math.random() * 10000); + const game: Phaser.Scene = GameScene.createFromUrl(this.room, this.MapUrlFile, gameSceneKey); this.scene.add(gameSceneKey, game, true, { initPosition: { @@ -603,14 +591,14 @@ export class GameScene extends ResizableScene implements CenterListener { connection.onActionableEvent((message => { const item = this.actionableItems.get(message.itemId); if (item === undefined) { - console.warn('Received an event about object "'+message.itemId+'" but cannot find this item on the map.'); + console.warn('Received an event about object "' + message.itemId + '" but cannot find this item on the map.'); return; } item.fire(message.event, message.state, message.parameters); })); // When connection is performed, let's connect SimplePeer - this.simplePeer = new SimplePeer(this.connection); + this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic); this.GlobalMessageManager = new GlobalMessageManager(this.connection); const self = this; diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 6d8e5c3d..eb65b555 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -10,6 +10,7 @@ const videoConstraint: boolean|MediaTrackConstraints = { export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void; export type StartScreenSharingCallback = (media: MediaStream) => void; export type StopScreenSharingCallback = (media: MediaStream) => void; +export type ReportCallback = (message: string) => void; // TODO: Split MediaManager in 2 classes: MediaManagerUI (in charge of HTML) and MediaManager (singleton in charge of the camera only) // TODO: verify that microphone event listeners are not triggered plenty of time NOW (since MediaManager is created many times!!!!) @@ -36,7 +37,6 @@ export class MediaManager { private cinemaBtn: HTMLDivElement; private monitorBtn: HTMLDivElement; - constructor() { this.myCamVideo = this.getElementByIdOrFail('myCamVideo'); @@ -91,17 +91,14 @@ export class MediaManager { } public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void { - this.updatedLocalStreamCallBacks.add(callback); } public onStartScreenSharing(callback: StartScreenSharingCallback): void { - this.startScreenSharingCallBacks.add(callback); } public onStopScreenSharing(callback: StopScreenSharingCallback): void { - this.stopScreenSharingCallBacks.add(callback); } @@ -342,8 +339,10 @@ export class MediaManager { /** * * @param userId + * @param reportCallBack + * @param userName */ - addActiveVideo(userId: string, userName: string = ""){ + addActiveVideo(userId: string, reportCallBack: ReportCallback|undefined, userName: string = ""){ this.webrtcInAudio.play(); userName = userName.toUpperCase(); @@ -355,12 +354,23 @@ export class MediaManager { ${userName} - + ` + + ((reportCallBack!==undefined)?``:'') + + + ` `; layoutManager.add(DivImportance.Normal, userId, html); + if (reportCallBack) { + const reportBtn = this.getElementByIdOrFail(`report-${userId}`); + reportBtn.addEventListener('click', (e: MouseEvent) => { + e.preventDefault(); + this.showReportModal(userId, userName, reportCallBack); + }); + } + this.remoteVideo.set(userId, this.getElementByIdOrFail(userId)); } @@ -542,6 +552,64 @@ export class MediaManager { return elem as T; } + private showReportModal(userId: string, userName: string, reportCallBack: ReportCallback){ + //create report text area + const mainContainer = this.getElementByIdOrFail('main-container'); + + const divReport = document.createElement('div'); + divReport.classList.add('modal-report-user'); + + const inputHidden = document.createElement('input'); + inputHidden.id = 'input-report-user'; + inputHidden.type = 'hidden'; + inputHidden.value = userId; + divReport.appendChild(inputHidden); + + const titleMessage = document.createElement('p'); + titleMessage.id = 'title-report-user'; + titleMessage.innerText = 'Open a report'; + divReport.appendChild(titleMessage); + + const bodyMessage = document.createElement('p'); + bodyMessage.id = 'body-report-user'; + bodyMessage.innerText = `You are about to open a report regarding an offensive conduct from user ${userName.toUpperCase()}. Please explain to us how you think ${userName.toUpperCase()} breached the code of conduct.`; + divReport.appendChild(bodyMessage); + + const imgReportUser = document.createElement('img'); + imgReportUser.id = 'img-report-user'; + imgReportUser.src = 'resources/logos/report.svg'; + divReport.appendChild(imgReportUser); + + const textareaUser = document.createElement('textarea'); + textareaUser.id = 'textarea-report-user'; + textareaUser.placeholder = 'Write ...'; + divReport.appendChild(textareaUser); + + const buttonReport = document.createElement('button'); + buttonReport.id = 'button-save-report-user'; + buttonReport.innerText = 'Report'; + buttonReport.addEventListener('click', () => { + if(!textareaUser.value){ + textareaUser.style.border = '1px solid red' + return; + } + reportCallBack(textareaUser.value); + divReport.remove(); + }); + divReport.appendChild(buttonReport); + + const buttonCancel = document.createElement('img'); + buttonCancel.id = 'cancel-report-user'; + buttonCancel.src = 'resources/logos/close.svg'; + buttonCancel.addEventListener('click', () => { + divReport.remove(); + }); + divReport.appendChild(buttonCancel); + + mainContainer.appendChild(divReport); + } + + } export const mediaManager = new MediaManager(); diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 890a4313..718837b7 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -29,8 +29,6 @@ export interface PeerConnectionListener { * This class manages connections to all the peers in the same group as me. */ export class SimplePeer { - private Connection: RoomConnection; - private WebRtcRoomId: string; private Users: Array = new Array(); private PeerScreenSharingConnectionArray: Map = new Map(); @@ -40,13 +38,12 @@ export class SimplePeer { private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback; private readonly peerConnectionListeners: Array = new Array(); - constructor(Connection: RoomConnection, WebRtcRoomId: string = "test-webrtc") { - this.Connection = Connection; - this.WebRtcRoomId = WebRtcRoomId; + constructor(private Connection: RoomConnection, private enableReporting: boolean) { // We need to go through this weird bound function pointer in order to be able to "free" this reference later. this.sendLocalVideoStreamCallback = this.sendLocalVideoStream.bind(this); this.sendLocalScreenSharingStreamCallback = this.sendLocalScreenSharingStream.bind(this); this.stopLocalScreenSharingStreamCallback = this.stopLocalScreenSharingStream.bind(this); + mediaManager.onUpdateLocalStream(this.sendLocalVideoStreamCallback); mediaManager.onStartScreenSharing(this.sendLocalScreenSharingStreamCallback); mediaManager.onStopScreenSharing(this.stopLocalScreenSharingStreamCallback); @@ -145,7 +142,12 @@ export class SimplePeer { } mediaManager.removeActiveVideo("" + user.userId); - mediaManager.addActiveVideo("" + user.userId, name); + + const reportCallback = this.enableReporting ? (comment: string) => { + this.reportUser(user.userId, comment); + }: undefined; + + mediaManager.addActiveVideo("" + user.userId, reportCallback, name); const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection); // When a connection is established to a video stream, and if a screen sharing is taking place, @@ -363,6 +365,13 @@ export class SimplePeer { } } + /** + * Triggered locally when clicking on the report button + */ + public reportUser(userId: number, message: string) { + this.Connection.emitReportPlayerMessage(userId, message) + } + private sendLocalScreenSharingStreamToUser(userId: number): void { // If a connection already exists with user (because it is already sharing a screen with us... let's use this connection) if (this.PeerScreenSharingConnectionArray.has(userId)) { diff --git a/messages/messages.proto b/messages/messages.proto index 935e033b..6e00e42a 100644 --- a/messages/messages.proto +++ b/messages/messages.proto @@ -48,6 +48,11 @@ message WebRtcSignalToServerMessage { string signal = 2; } +message ReportPlayerMessage { + int32 reportedUserId = 1; + string reportComment = 2; +} + message ClientToServerMessage { oneof message { UserMovesMessage userMovesMessage = 2; @@ -59,6 +64,7 @@ message ClientToServerMessage { WebRtcSignalToServerMessage webRtcScreenSharingSignalToServerMessage = 8; PlayGlobalMessage playGlobalMessage = 9; StopGlobalMessage stopGlobalMessage = 10; + ReportPlayerMessage reportPlayerMessage = 11; } } @@ -157,6 +163,10 @@ message WebRtcSignalToClientMessage { string signal = 2; } +message TeleportMessageMessage{ + string map = 1; +} + message ServerToClientMessage { oneof message { BatchMessage batchMessage = 1; @@ -168,5 +178,6 @@ message ServerToClientMessage { WebRtcDisconnectMessage webRtcDisconnectMessage = 7; PlayGlobalMessage playGlobalMessage = 8; StopGlobalMessage stopGlobalMessage = 9; + TeleportMessageMessage teleportMessageMessage = 10; } }