This commit is contained in:
David Négrier 2020-09-16 11:41:03 +02:00
commit 3a9196fb82
37 changed files with 1601 additions and 213 deletions

View file

@ -4,10 +4,10 @@ import * as http from "http";
import {MessageUserPosition, Point} from "../Model/Websocket/MessageUserPosition"; //TODO fix import by "_Model/.."
import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.."
import Jwt, {JsonWebTokenError} from "jsonwebtoken";
import {SECRET_KEY, MINIMUM_DISTANCE, GROUP_RADIUS} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..."
import {SECRET_KEY, MINIMUM_DISTANCE, GROUP_RADIUS, ALLOW_ARTILLERY} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..."
import {World} from "../Model/World";
import {Group} from "_Model/Group";
import {UserInterface} from "_Model/UserInterface";
import {Group} from "../Model/Group";
import {UserInterface} from "../Model/UserInterface";
import {isSetPlayerDetailsMessage,} from "../Model/Websocket/SetPlayerDetailsMessage";
import {MessageUserJoined} from "../Model/Websocket/MessageUserJoined";
import {MessageUserMoved} from "../Model/Websocket/MessageUserMoved";
@ -19,12 +19,15 @@ import {isPointInterface, PointInterface} from "../Model/Websocket/PointInterfac
import {isWebRtcSignalMessageInterface} from "../Model/Websocket/WebRtcSignalMessage";
import {UserInGroupInterface} from "../Model/Websocket/UserInGroupInterface";
import {isItemEventMessageInterface} from "../Model/Websocket/ItemEventMessage";
import {uuid} from 'uuidv4';
import {isUserMovesInterface} from "../Model/Websocket/UserMovesMessage";
import {isViewport} from "../Model/Websocket/ViewportMessage";
enum SockerIoEvent {
CONNECTION = "connection",
DISCONNECT = "disconnect",
JOIN_ROOM = "join-room", // bi-directional
USER_POSITION = "user-position", // bi-directional
USER_POSITION = "user-position", // From client to server
USER_MOVED = "user-moved", // From server to client
USER_LEFT = "user-left", // From server to client
WEBRTC_SIGNAL = "webrtc-signal",
@ -36,6 +39,21 @@ enum SockerIoEvent {
GROUP_DELETE = "group-delete",
SET_PLAYER_DETAILS = "set-player-details",
ITEM_EVENT = 'item-event',
SET_SILENT = "set_silent", // Set or unset the silent mode for this user.
SET_VIEWPORT = "set-viewport",
BATCH = "batch",
}
function emitInBatch(socket: ExSocketInterface, event: string | symbol, payload: unknown): void {
socket.batchedMessages.push({ event, payload});
if (socket.batchTimeout === null) {
socket.batchTimeout = setTimeout(() => {
socket.emit(SockerIoEvent.BATCH, socket.batchedMessages);
socket.batchedMessages = [];
socket.batchTimeout = null;
}, 100);
}
}
export class IoSocketController {
@ -61,10 +79,25 @@ export class IoSocketController {
// Authentication with token. it will be decoded and stored in the socket.
// Completely commented for now, as we do not use the "/login" route at all.
this.Io.use((socket: Socket, next) => {
console.log(socket.handshake.query.token);
if (!socket.handshake.query || !socket.handshake.query.token) {
console.error('An authentication error happened, a user tried to connect without a token.');
return next(new Error('Authentication error'));
}
if(socket.handshake.query.token === 'test'){
if (ALLOW_ARTILLERY) {
(socket as ExSocketInterface).token = socket.handshake.query.token;
(socket as ExSocketInterface).userId = uuid();
(socket as ExSocketInterface).isArtillery = true;
console.log((socket as ExSocketInterface).userId);
next();
return;
} else {
console.warn("In order to perform a load-testing test on this environment, you must set the ALLOW_ARTILLERY environment variable to 'true'");
next();
}
}
(socket as ExSocketInterface).isArtillery = false;
if(this.searchClientByToken(socket.handshake.query.token)){
console.error('An authentication error happened, a user tried to connect while its token is already connected.');
return next(new Error('Authentication error'));
@ -137,6 +170,11 @@ export class IoSocketController {
ioConnection() {
this.Io.on(SockerIoEvent.CONNECTION, (socket: Socket) => {
const client : ExSocketInterface = socket as ExSocketInterface;
client.batchedMessages = [];
client.batchTimeout = null;
client.emitInBatch = (event: string | symbol, payload: unknown): void => {
emitInBatch(client, event, payload);
}
this.sockets.set(client.userId, client);
// Let's log server load when a user joins
@ -156,6 +194,7 @@ export class IoSocketController {
y: user y position on map
*/
socket.on(SockerIoEvent.JOIN_ROOM, (message: unknown, answerFn): void => {
console.log(SockerIoEvent.JOIN_ROOM, message);
try {
if (!isJoinRoomMessageInterface(message)) {
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid JOIN_ROOM message.'});
@ -176,28 +215,30 @@ export class IoSocketController {
//join new previous room
const world = this.joinRoom(Client, roomId, message.position);
//add function to refresh position user in real time.
//this.refreshUserPosition(Client);
const messageUserJoined = new MessageUserJoined(Client.userId, Client.name, Client.characterLayers, Client.position);
socket.to(roomId).emit(SockerIoEvent.JOIN_ROOM, messageUserJoined);
// The answer shall contain the list of all users of the room with their positions:
const listOfUsers = Array.from(world.getUsers(), ([key, user]) => {
const users = world.setViewport(Client, message.viewport);
const listOfUsers = users.map((user: UserInterface) => {
const player: ExSocketInterface|undefined = this.sockets.get(user.id);
if (player === undefined) {
console.warn('Something went wrong. The World contains a user "'+user.id+"' but this user does not exist in the sockets list!");
return null;
}
return new MessageUserPosition(user.id, player.name, player.characterLayers, player.position);
}).filter((item: MessageUserPosition|null) => item !== null);
}, users);
const listOfItems: {[itemId: string]: unknown} = {};
for (const [itemId, item] of world.getItemsState().entries()) {
listOfItems[itemId] = item;
}
//console.warn('ANSWER PLAYER POSITIONS', listOfUsers);
if (answerFn === undefined && ALLOW_ARTILLERY === true) {
/*console.error("TYPEOF answerFn", typeof(answerFn));
console.error("answerFn", answerFn);
process.exit(1)*/
// For some reason, answerFn can be undefined if we use Artillery (?)
return;
}
answerFn({
users: listOfUsers,
items: listOfItems
@ -208,28 +249,53 @@ export class IoSocketController {
}
});
socket.on(SockerIoEvent.USER_POSITION, (position: unknown): void => {
socket.on(SockerIoEvent.SET_VIEWPORT, (message: unknown): void => {
try {
if (!isPointInterface(position)) {
//console.log('SET_VIEWPORT')
if (!isViewport(message)) {
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_VIEWPORT message.'});
console.warn('Invalid SET_VIEWPORT message received: ', message);
return;
}
const Client = (socket as ExSocketInterface);
Client.viewport = message;
const world = this.Worlds.get(Client.roomId);
if (!world) {
console.error("In SET_VIEWPORT, could not find world with id '", Client.roomId, "'");
return;
}
world.setViewport(Client, Client.viewport);
} catch (e) {
console.error('An error occurred on "SET_VIEWPORT" event');
console.error(e);
}
});
socket.on(SockerIoEvent.USER_POSITION, (userMovesMessage: unknown): void => {
//console.log(SockerIoEvent.USER_POSITION, userMovesMessage);
try {
if (!isUserMovesInterface(userMovesMessage)) {
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid USER_POSITION message.'});
console.warn('Invalid USER_POSITION message received: ', position);
console.warn('Invalid USER_POSITION message received: ', userMovesMessage);
return;
}
const Client = (socket as ExSocketInterface);
// sending to all clients in room except sender
Client.position = position;
Client.position = userMovesMessage.position;
Client.viewport = userMovesMessage.viewport;
// update position in the world
const world = this.Worlds.get(Client.roomId);
if (!world) {
console.error("Could not find world with id '", Client.roomId, "'");
console.error("In USER_POSITION, could not find world with id '", Client.roomId, "'");
return;
}
world.updatePosition(Client, position);
socket.to(Client.roomId).emit(SockerIoEvent.USER_MOVED, new MessageUserMoved(Client.userId, Client.position));
world.updatePosition(Client, Client.position);
world.setViewport(Client, Client.viewport);
} catch (e) {
console.error('An error occurred on "user_position" event');
console.error(e);
@ -275,6 +341,7 @@ export class IoSocketController {
// Let's send the user id to the user
socket.on(SockerIoEvent.SET_PLAYER_DETAILS, (playerDetails: unknown, answerFn) => {
console.log(SockerIoEvent.SET_PLAYER_DETAILS, playerDetails);
if (!isSetPlayerDetailsMessage(playerDetails)) {
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_PLAYER_DETAILS message.'});
console.warn('Invalid SET_PLAYER_DETAILS message received: ', playerDetails);
@ -283,7 +350,34 @@ export class IoSocketController {
const Client = (socket as ExSocketInterface);
Client.name = playerDetails.name;
Client.characterLayers = playerDetails.characterLayers;
answerFn(Client.userId);
// Artillery fails when receiving an acknowledgement that is not a JSON object
if (!Client.isArtillery) {
answerFn(Client.userId);
}
});
socket.on(SockerIoEvent.SET_SILENT, (silent: unknown) => {
console.log(SockerIoEvent.SET_SILENT, silent);
if (typeof silent !== "boolean") {
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_SILENT message.'});
console.warn('Invalid SET_SILENT message received: ', silent);
return;
}
try {
const Client = (socket as ExSocketInterface);
// update position in the world
const world = this.Worlds.get(Client.roomId);
if (!world) {
console.error("In SET_SILENT, could not find world with id '", Client.roomId, "'");
return;
}
world.setSilent(Client, silent);
} catch (e) {
console.error('An error occurred on "SET_SILENT"');
console.error(e);
}
});
socket.on(SockerIoEvent.ITEM_EVENT, (itemEvent: unknown) => {
@ -359,8 +453,6 @@ export class IoSocketController {
// leave previous room and world
if(Client.roomId){
try {
Client.to(Client.roomId).emit(SockerIoEvent.USER_LEFT, Client.userId);
//user leave previous world
const world: World | undefined = this.Worlds.get(Client.roomId);
if (world) {
@ -396,6 +488,25 @@ export class IoSocketController {
this.sendUpdateGroupEvent(group);
}, (groupUuid: string, lastUser: UserInterface) => {
this.sendDeleteGroupEvent(groupUuid, lastUser);
}, (user, listener) => {
const clientUser = this.searchClientByIdOrFail(user.id);
const clientListener = this.searchClientByIdOrFail(listener.id);
const messageUserJoined = new MessageUserJoined(clientUser.userId, clientUser.name, clientUser.characterLayers, clientUser.position);
clientListener.emit(SockerIoEvent.JOIN_ROOM, messageUserJoined);
//console.log("Sending JOIN_ROOM event");
}, (user, position, listener) => {
const clientUser = this.searchClientByIdOrFail(user.id);
const clientListener = this.searchClientByIdOrFail(listener.id);
clientListener.emitInBatch(SockerIoEvent.USER_MOVED, new MessageUserMoved(clientUser.userId, clientUser.position));
//console.log("Sending USER_MOVED event");
}, (user, listener) => {
const clientUser = this.searchClientByIdOrFail(user.id);
const clientListener = this.searchClientByIdOrFail(listener.id);
clientListener.emit(SockerIoEvent.USER_LEFT, clientUser.userId);
//console.log("Sending USER_LEFT event");
});
this.Worlds.set(roomId, world);
}