Merge pull request #342 from thecodingmachine/ban-message

Ban feature
This commit is contained in:
David Négrier 2020-10-20 10:26:14 +02:00 committed by GitHub
commit a4a19a60e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 227 additions and 14 deletions

View file

@ -12,14 +12,13 @@ import {
WebRtcSignalToServerMessage,
PlayGlobalMessage,
ReportPlayerMessage,
QueryJitsiJwtMessage,
SendJitsiJwtMessage,
QueryJitsiJwtMessage
} from "../Messages/generated/messages_pb";
import {UserMovesMessage} from "../Messages/generated/messages_pb";
import {TemplatedApp} from "uWebSockets.js"
import {parse} from "query-string";
import {jwtTokenManager} from "../Services/JWTTokenManager";
import {adminApi} from "../Services/AdminApi";
import {adminApi, fetchMemberDataByUuidResponse} from "../Services/AdminApi";
import {socketManager} from "../Services/SocketManager";
import {emitInBatch, resetPing} from "../Services/IoSocketHelpers";
import Jwt from "jsonwebtoken";
@ -72,7 +71,33 @@ export class IoSocketController {
clientEventsEmitter.registerToClientLeave(ws.clientLeaveCallback);
},
message: (ws, arrayBuffer, isBinary): void => {
console.log('m', ws); //todo: add admin actions such as ban here
try {
//TODO refactor message type and data
const message: {event: string, message: {type: string, message: unknown, userUuid: string}} =
JSON.parse(new TextDecoder("utf-8").decode(new Uint8Array(arrayBuffer)));
if(message.event === 'user-message') {
const messageToEmit = (message.message as { message: string, type: string, userUuid: string });
switch (message.message.type) {
case 'ban': {
socketManager.emitSendUserMessage(messageToEmit);
break;
}
case 'banned': {
const socketUser = socketManager.emitSendUserMessage(messageToEmit);
setTimeout(() => {
socketUser.close();
}, 10000);
break;
}
default: {
break;
}
}
}
}catch (err) {
console.error(err);
}
},
close: (ws, code, message) => {
//todo make sure this code unregister the right listeners
@ -206,6 +231,23 @@ export class IoSocketController {
const client = this.initClient(ws); //todo: into the upgrade instead?
socketManager.handleJoinRoom(client);
resetPing(client);
//get data information and shwo messages
adminApi.fetchMemberDataByUuid(client.userUuid).then((res: fetchMemberDataByUuidResponse) => {
if (!res.messages) {
return;
}
res.messages.forEach((c: unknown) => {
const messageToSend = c as { type: string, message: string };
socketManager.emitSendUserMessage({
userUuid: client.userUuid,
type: messageToSend.type,
message: messageToSend.message
})
});
}).catch((err) => {
console.error('fetchMemberDataByUuid => err', err);
});
},
message: (ws, arrayBuffer, isBinary): void => {
const client = ws as ExSocketInterface;

View file

@ -9,11 +9,13 @@ export interface AdminApiData {
tags: string[]
policy_type: number
userUuid: string
messages?: unknown[]
}
export interface fetchMemberDataByUuidResponse {
uuid: string;
tags: string[];
messages: unknown[];
}
class AdminApi {
@ -32,9 +34,9 @@ class AdminApi {
params.roomSlug = roomSlug;
}
const res = await Axios.get(ADMIN_API_URL+'/api/map',
const res = await Axios.get(ADMIN_API_URL + '/api/map',
{
headers: {"Authorization" : `${ADMIN_API_TOKEN}`},
headers: {"Authorization": `${ADMIN_API_TOKEN}`},
params
}
)
@ -45,7 +47,7 @@ class AdminApi {
if (!ADMIN_API_URL) {
return Promise.reject('No admin backoffice set!');
}
const res = await Axios.get(ADMIN_API_URL+'/membership/'+uuid,
const res = await Axios.get(ADMIN_API_URL+'/api/membership/'+uuid,
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
)
return res.data;
@ -61,6 +63,17 @@ class AdminApi {
)
return res.data;
}
async fetchCheckUserByToken(organizationMemberToken: string): Promise<AdminApiData> {
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) {
return Axios.post(`${ADMIN_API_URL}/api/report`, {

View file

@ -2,6 +2,7 @@ import {ALLOW_ARTILLERY, SECRET_KEY} from "../Enum/EnvironmentVariable";
import {uuid} from "uuidv4";
import Jwt from "jsonwebtoken";
import {TokenInterface} from "../Controller/AuthenticateController";
import {adminApi, AdminApiData} from "../Services/AdminApi";
class JWTTokenManager {
@ -32,7 +33,7 @@ class JWTTokenManager {
const tokenInterface = tokenDecoded as TokenInterface;
if (err) {
console.error('An authentication error happened, invalid JsonWebToken.', err);
reject(new Error('An authentication error happened, invalid JsonWebToken. '+err.message));
reject(new Error('An authentication error happened, invalid JsonWebToken. ' + err.message));
return;
}
if (tokenDecoded === undefined) {
@ -41,12 +42,23 @@ class JWTTokenManager {
return;
}
//verify token
if (!this.isValidToken(tokenInterface)) {
reject(new Error('Authentication error, invalid token structure.'));
return;
}
resolve(tokenInterface.userUuid);
//verify user in admin
adminApi.fetchCheckUserByToken(tokenInterface.userUuid).then(() => {
resolve(tokenInterface.userUuid);
}).catch((err) => {
//anonymous user
if(err.response && err.response.status && err.response.status === 404){
resolve(tokenInterface.userUuid);
return;
}
reject(new Error('Authentication error, invalid token structure. ' + err));
});
});
});
}

View file

@ -19,7 +19,11 @@ import {
UserMovesMessage,
ViewportMessage, WebRtcDisconnectMessage,
WebRtcSignalToClientMessage,
WebRtcSignalToServerMessage, WebRtcStartMessage, QueryJitsiJwtMessage, SendJitsiJwtMessage
WebRtcSignalToServerMessage,
WebRtcStartMessage,
QueryJitsiJwtMessage,
SendJitsiJwtMessage,
SendUserMessage
} from "../Messages/generated/messages_pb";
import {PointInterface} from "../Model/Websocket/PointInterface";
import {User} from "../Model/User";
@ -668,6 +672,25 @@ class SocketManager {
client.send(serverToClientMessage.serializeBinary().buffer, true);
}
public emitSendUserMessage(messageToSend: {userUuid: string, message: string, type: string}): ExSocketInterface {
const socket = this.searchClientByUuid(messageToSend.userUuid);
if(!socket){
throw 'socket was not found';
}
const sendUserMessage = new SendUserMessage();
sendUserMessage.setMessage(messageToSend.message);
sendUserMessage.setType(messageToSend.type);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendusermessage(sendUserMessage);
if (!socket.disconnecting) {
socket.send(serverToClientMessage.serializeBinary().buffer, true);
}
return socket;
}
}
export const socketManager = new SocketManager();

View file

@ -125,6 +125,9 @@
<audio id="audio-webrtc-in">
<source src="/resources/objects/webrtc-in.mp3" type="audio/mp3">
</audio>
<audio id="report-message">
<source src="/resources/objects/report-message.mp3" type="audio/mp3">
</audio>
</body>
</html>

Binary file not shown.

View file

@ -654,4 +654,6 @@ div.modal-report-user{
width: 100%;
text-align: left;
padding: 30px;
max-width: calc(800px - 60px); /* size of modal - padding*/
}

View file

@ -0,0 +1,67 @@
import {TypeMessageInterface} from "./UserMessageManager";
import {HtmlUtils} from "../WebRtc/HtmlUtils";
export class TypeMessageExt implements TypeMessageInterface{
private nbSecond = 0;
private maxNbSecond = 10;
private titleMessage = 'IMPORTANT !';
showMessage(message: string): void {
const div : HTMLDivElement = document.createElement('div');
div.classList.add('modal-report-user');
div.id = 'report-message-user';
div.style.backgroundColor = '#000000e0';
const img : HTMLImageElement = document.createElement('img');
img.src = 'resources/logos/report.svg';
div.appendChild(img);
const title : HTMLParagraphElement = document.createElement('p');
title.id = 'title-report-user';
title.innerText = `${this.titleMessage} (${this.maxNbSecond})`;
div.appendChild(title);
const p : HTMLParagraphElement = document.createElement('p');
p.id = 'body-report-user'
p.innerText = message;
div.appendChild(p);
const mainSectionDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
mainSectionDiv.appendChild(div);
const reportMessageAudio = HtmlUtils.getElementByIdOrFail<HTMLAudioElement>('report-message');
reportMessageAudio.play();
this.nbSecond = this.maxNbSecond;
setTimeout((c) => {
this.forMessage(title);
}, 1000);
}
forMessage(title: HTMLParagraphElement){
this.nbSecond -= 1;
title.innerText = `${this.titleMessage} (${this.nbSecond})`;
if(this.nbSecond > 0){
setTimeout(() => {
this.forMessage(title);
}, 1000);
}else{
title.innerText = this.titleMessage;
const imgCancel : HTMLImageElement = document.createElement('img');
imgCancel.id = 'cancel-report-user';
imgCancel.src = 'resources/logos/close.svg';
const div = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('report-message-user');
div.appendChild(imgCancel);
imgCancel.addEventListener('click', () => {
div.remove();
});
}
}
}
export class Ban extends TypeMessageExt {
}
export class Banned extends TypeMessageExt {
}

View file

@ -0,0 +1,36 @@
import {RoomConnection} from "../Connexion/RoomConnection";
import * as TypeMessages from "./TypeMessage";
export interface TypeMessageInterface {
showMessage(message: string): void;
}
export class UserMessageManager {
typeMessages: Map<string, TypeMessageInterface> = new Map<string, TypeMessageInterface>();
constructor(private Connection: RoomConnection) {
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) => {
this.showMessage(type, message);
});
}
showMessage(type: string, message: string) {
const classTypeMessage = this.typeMessages.get(type.toLowerCase());
if (!classTypeMessage) {
console.error('Message unknown');
return;
}
classTypeMessage.showMessage(message);
}
}

View file

@ -27,6 +27,7 @@ export enum EventMessage{
STOP_GLOBAL_MESSAGE = "stop-global-message",
TELEPORT = "teleport",
USER_MESSAGE = "user-message",
START_JITSI_ROOM = "start-jitsi-room",
}

View file

@ -22,7 +22,7 @@ import {
WebRtcSignalToServerMessage,
WebRtcStartMessage,
ReportPlayerMessage,
TeleportMessageMessage, QueryJitsiJwtMessage, SendJitsiJwtMessage
TeleportMessageMessage, QueryJitsiJwtMessage, SendJitsiJwtMessage, SendUserMessage
} from "../Messages/generated/messages_pb"
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
@ -35,8 +35,6 @@ import {
RoomJoinedMessageInterface,
ViewportInterface, WebRtcDisconnectMessageInterface,
WebRtcSignalReceivedMessageInterface,
WebRtcSignalSentMessageInterface,
WebRtcStartMessageInterface
} from "./ConnexionModels";
export class RoomConnection implements RoomConnection {
@ -152,6 +150,8 @@ export class RoomConnection implements RoomConnection {
this.dispatch(EventMessage.TELEPORT, message.getTeleportmessagemessage());
} else if (message.hasSendjitsijwtmessage()) {
this.dispatch(EventMessage.START_JITSI_ROOM, message.getSendjitsijwtmessage());
} else if (message.hasSendusermessage()) {
this.dispatch(EventMessage.USER_MESSAGE, message.getSendusermessage());
} else {
throw new Error('Unknown message received');
}
@ -479,8 +479,13 @@ 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){
console.log('emitGlobalMessage', message);
const playGlobalMessage = new PlayGlobalMessage();
playGlobalMessage.setId(message.id);
playGlobalMessage.setType(message.type);

View file

@ -51,6 +51,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 {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMessageManager";
import {ResizableScene} from "../Login/ResizableScene";
import {Room} from "../../Connexion/Room";
@ -114,6 +115,7 @@ export class GameScene extends ResizableScene implements CenterListener {
private connection!: RoomConnection;
private simplePeer!: SimplePeer;
private GlobalMessageManager!: GlobalMessageManager;
private UserMessageManager!: UserMessageManager;
private ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager;
private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>;
private connectionAnswerPromiseResolve!: (value?: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>) => void;
@ -600,6 +602,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.GlobalMessageManager = new GlobalMessageManager(this.connection);
this.UserMessageManager = new UserMessageManager(this.connection);
const self = this;
this.simplePeer.registerPeerConnectionListener({

View file

@ -178,6 +178,11 @@ message SendJitsiJwtMessage {
string jwt = 2;
}
message SendUserMessage{
string type = 1;
string message = 2;
}
message ServerToClientMessage {
oneof message {
BatchMessage batchMessage = 1;
@ -191,5 +196,6 @@ message ServerToClientMessage {
StopGlobalMessage stopGlobalMessage = 9;
TeleportMessageMessage teleportMessageMessage = 10;
SendJitsiJwtMessage sendJitsiJwtMessage = 11;
SendUserMessage sendUserMessage = 12;
}
}