Merge pull request #169 from thecodingmachine/stricter_lint
Enabling stricter lint: forbidding usage of any
This commit is contained in:
commit
b24541f9b8
@ -7,7 +7,8 @@
|
|||||||
},
|
},
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:@typescript-eslint/eslint-recommended"
|
"plugin:@typescript-eslint/eslint-recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended-requiring-type-checking"
|
||||||
],
|
],
|
||||||
"globals": {
|
"globals": {
|
||||||
"Atomics": "readonly",
|
"Atomics": "readonly",
|
||||||
@ -16,12 +17,14 @@
|
|||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 2018,
|
"ecmaVersion": 2018,
|
||||||
"sourceType": "module"
|
"sourceType": "module",
|
||||||
|
"project": "./tsconfig.json"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"@typescript-eslint"
|
"@typescript-eslint"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-unused-vars": "off"
|
"no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "error"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -16,7 +16,8 @@
|
|||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/thecodingmachine/workadventure.git"
|
"url": "git+https://github.com/thecodingmachine/workadventure.git"
|
||||||
},
|
},
|
||||||
"contributors": [{
|
"contributors": [
|
||||||
|
{
|
||||||
"name": "Grégoire Parant",
|
"name": "Grégoire Parant",
|
||||||
"email": "g.parant@thecodingmachine.com"
|
"email": "g.parant@thecodingmachine.com"
|
||||||
},
|
},
|
||||||
@ -27,7 +28,8 @@
|
|||||||
{
|
{
|
||||||
"name": "Arthmaël Poly",
|
"name": "Arthmaël Poly",
|
||||||
"email": "a.poly@thecodingmachine.com"
|
"email": "a.poly@thecodingmachine.com"
|
||||||
}],
|
}
|
||||||
|
],
|
||||||
"license": "SEE LICENSE IN LICENSE.txt",
|
"license": "SEE LICENSE IN LICENSE.txt",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/thecodingmachine/workadventure/issues"
|
"url": "https://github.com/thecodingmachine/workadventure/issues"
|
||||||
@ -41,6 +43,7 @@
|
|||||||
"@types/uuidv4": "^5.0.0",
|
"@types/uuidv4": "^5.0.0",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
"generic-type-guard": "^3.2.0",
|
||||||
"http-status-codes": "^1.4.0",
|
"http-status-codes": "^1.4.0",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"prom-client": "^12.0.0",
|
"prom-client": "^12.0.0",
|
||||||
|
@ -4,6 +4,11 @@ import {BAD_REQUEST, OK} from "http-status-codes";
|
|||||||
import {SECRET_KEY, URL_ROOM_STARTED} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..."
|
import {SECRET_KEY, URL_ROOM_STARTED} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..."
|
||||||
import { uuid } from 'uuidv4';
|
import { uuid } from 'uuidv4';
|
||||||
|
|
||||||
|
export interface TokenInterface {
|
||||||
|
name: string,
|
||||||
|
userId: string
|
||||||
|
}
|
||||||
|
|
||||||
export class AuthenticateController {
|
export class AuthenticateController {
|
||||||
App : Application;
|
App : Application;
|
||||||
|
|
||||||
@ -16,15 +21,15 @@ export class AuthenticateController {
|
|||||||
login(){
|
login(){
|
||||||
// For now, let's completely forget the /login route.
|
// For now, let's completely forget the /login route.
|
||||||
this.App.post("/login", (req: Request, res: Response) => {
|
this.App.post("/login", (req: Request, res: Response) => {
|
||||||
let param = req.body;
|
const param = req.body;
|
||||||
/*if(!param.name){
|
/*if(!param.name){
|
||||||
return res.status(BAD_REQUEST).send({
|
return res.status(BAD_REQUEST).send({
|
||||||
message: "email parameter is empty"
|
message: "email parameter is empty"
|
||||||
});
|
});
|
||||||
}*/
|
}*/
|
||||||
//TODO check user email for The Coding Machine game
|
//TODO check user email for The Coding Machine game
|
||||||
let userId = uuid();
|
const userId = uuid();
|
||||||
let token = Jwt.sign({name: param.name, userId: userId}, SECRET_KEY, {expiresIn: '24h'});
|
const token = Jwt.sign({name: param.name, userId: userId} as TokenInterface, SECRET_KEY, {expiresIn: '24h'});
|
||||||
return res.status(OK).send({
|
return res.status(OK).send({
|
||||||
token: token,
|
token: token,
|
||||||
mapUrlStart: URL_ROOM_STARTED,
|
mapUrlStart: URL_ROOM_STARTED,
|
||||||
|
@ -8,12 +8,17 @@ import {SECRET_KEY, MINIMUM_DISTANCE, GROUP_RADIUS} from "../Enum/EnvironmentVar
|
|||||||
import {World} from "../Model/World";
|
import {World} from "../Model/World";
|
||||||
import {Group} from "_Model/Group";
|
import {Group} from "_Model/Group";
|
||||||
import {UserInterface} from "_Model/UserInterface";
|
import {UserInterface} from "_Model/UserInterface";
|
||||||
import {SetPlayerDetailsMessage} from "_Model/Websocket/SetPlayerDetailsMessage";
|
import {isSetPlayerDetailsMessage,} from "../Model/Websocket/SetPlayerDetailsMessage";
|
||||||
import {MessageUserJoined} from "../Model/Websocket/MessageUserJoined";
|
import {MessageUserJoined} from "../Model/Websocket/MessageUserJoined";
|
||||||
import {MessageUserMoved} from "../Model/Websocket/MessageUserMoved";
|
import {MessageUserMoved} from "../Model/Websocket/MessageUserMoved";
|
||||||
import si from "systeminformation";
|
import si from "systeminformation";
|
||||||
import {Gauge} from "prom-client";
|
import {Gauge} from "prom-client";
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
import {TokenInterface} from "../Controller/AuthenticateController";
|
||||||
|
import {isJoinRoomMessageInterface} from "../Model/Websocket/JoinRoomMessage";
|
||||||
|
import {isPointInterface, PointInterface} from "../Model/Websocket/PointInterface";
|
||||||
|
import {isWebRtcSignalMessageInterface} from "../Model/Websocket/WebRtcSignalMessage";
|
||||||
|
import {UserInGroupInterface} from "../Model/Websocket/UserInGroupInterface";
|
||||||
|
|
||||||
enum SockerIoEvent {
|
enum SockerIoEvent {
|
||||||
CONNECTION = "connection",
|
CONNECTION = "connection",
|
||||||
@ -23,7 +28,6 @@ enum SockerIoEvent {
|
|||||||
USER_MOVED = "user-moved", // From server to client
|
USER_MOVED = "user-moved", // From server to client
|
||||||
USER_LEFT = "user-left", // From server to client
|
USER_LEFT = "user-left", // From server to client
|
||||||
WEBRTC_SIGNAL = "webrtc-signal",
|
WEBRTC_SIGNAL = "webrtc-signal",
|
||||||
WEBRTC_OFFER = "webrtc-offer",
|
|
||||||
WEBRTC_START = "webrtc-start",
|
WEBRTC_START = "webrtc-start",
|
||||||
WEBRTC_DISCONNECT = "webrtc-disconect",
|
WEBRTC_DISCONNECT = "webrtc-disconect",
|
||||||
MESSAGE_ERROR = "message-error",
|
MESSAGE_ERROR = "message-error",
|
||||||
@ -56,16 +60,24 @@ export class IoSocketController {
|
|||||||
// Completely commented for now, as we do not use the "/login" route at all.
|
// Completely commented for now, as we do not use the "/login" route at all.
|
||||||
this.Io.use((socket: Socket, next) => {
|
this.Io.use((socket: Socket, next) => {
|
||||||
if (!socket.handshake.query || !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'));
|
return next(new Error('Authentication error'));
|
||||||
}
|
}
|
||||||
if(this.searchClientByToken(socket.handshake.query.token)){
|
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'));
|
return next(new Error('Authentication error'));
|
||||||
}
|
}
|
||||||
Jwt.verify(socket.handshake.query.token, SECRET_KEY, (err: JsonWebTokenError, tokenDecoded: any) => {
|
Jwt.verify(socket.handshake.query.token, SECRET_KEY, (err: JsonWebTokenError, tokenDecoded: object) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
console.error('An authentication error happened, invalid JsonWebToken.', err);
|
||||||
return next(new Error('Authentication error'));
|
return next(new Error('Authentication error'));
|
||||||
}
|
}
|
||||||
(socket as ExSocketInterface).token = tokenDecoded;
|
|
||||||
|
if (!this.isValidToken(tokenDecoded)) {
|
||||||
|
return next(new Error('Authentication error, invalid token structure'));
|
||||||
|
}
|
||||||
|
|
||||||
|
(socket as ExSocketInterface).token = socket.handshake.query.token;
|
||||||
(socket as ExSocketInterface).userId = tokenDecoded.userId;
|
(socket as ExSocketInterface).userId = tokenDecoded.userId;
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
@ -74,14 +86,24 @@ export class IoSocketController {
|
|||||||
this.ioConnection();
|
this.ioConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isValidToken(token: object): token is TokenInterface {
|
||||||
|
if (typeof((token as TokenInterface).userId) !== 'string') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (typeof((token as TokenInterface).name) !== 'string') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param token
|
* @param token
|
||||||
*/
|
*/
|
||||||
searchClientByToken(token: string): ExSocketInterface | null {
|
searchClientByToken(token: string): ExSocketInterface | null {
|
||||||
let clients: Array<any> = Object.values(this.Io.sockets.sockets);
|
const clients: ExSocketInterface[] = Object.values(this.Io.sockets.sockets) as ExSocketInterface[];
|
||||||
for (let i = 0; i < clients.length; i++) {
|
for (let i = 0; i < clients.length; i++) {
|
||||||
let client: ExSocketInterface = clients[i];
|
const client = clients[i];
|
||||||
if (client.token !== token) {
|
if (client.token !== token) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -93,9 +115,9 @@ export class IoSocketController {
|
|||||||
private sendUpdateGroupEvent(group: Group): void {
|
private sendUpdateGroupEvent(group: Group): void {
|
||||||
// Let's get the room of the group. To do this, let's get anyone in the group and find its room.
|
// Let's get the room of the group. To do this, let's get anyone in the group and find its room.
|
||||||
// Note: this is suboptimal
|
// Note: this is suboptimal
|
||||||
let userId = group.getUsers()[0].id;
|
const userId = group.getUsers()[0].id;
|
||||||
let client: ExSocketInterface = this.searchClientByIdOrFail(userId);
|
const client: ExSocketInterface = this.searchClientByIdOrFail(userId);
|
||||||
let roomId = client.roomId;
|
const roomId = client.roomId;
|
||||||
this.Io.in(roomId).emit(SockerIoEvent.GROUP_CREATE_UPDATE, {
|
this.Io.in(roomId).emit(SockerIoEvent.GROUP_CREATE_UPDATE, {
|
||||||
position: group.getPosition(),
|
position: group.getPosition(),
|
||||||
groupId: group.getId()
|
groupId: group.getId()
|
||||||
@ -104,19 +126,19 @@ export class IoSocketController {
|
|||||||
|
|
||||||
private sendDeleteGroupEvent(uuid: string, lastUser: UserInterface): void {
|
private sendDeleteGroupEvent(uuid: string, lastUser: UserInterface): void {
|
||||||
// Let's get the room of the group. To do this, let's get anyone in the group and find its room.
|
// Let's get the room of the group. To do this, let's get anyone in the group and find its room.
|
||||||
let userId = lastUser.id;
|
const userId = lastUser.id;
|
||||||
let client: ExSocketInterface = this.searchClientByIdOrFail(userId);
|
const client: ExSocketInterface = this.searchClientByIdOrFail(userId);
|
||||||
let roomId = client.roomId;
|
const roomId = client.roomId;
|
||||||
this.Io.in(roomId).emit(SockerIoEvent.GROUP_DELETE, uuid);
|
this.Io.in(roomId).emit(SockerIoEvent.GROUP_DELETE, uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
ioConnection() {
|
ioConnection() {
|
||||||
this.Io.on(SockerIoEvent.CONNECTION, (socket: Socket) => {
|
this.Io.on(SockerIoEvent.CONNECTION, (socket: Socket) => {
|
||||||
let client : ExSocketInterface = socket as ExSocketInterface;
|
const client : ExSocketInterface = socket as ExSocketInterface;
|
||||||
this.sockets.set(client.userId, client);
|
this.sockets.set(client.userId, client);
|
||||||
|
|
||||||
// Let's log server load when a user joins
|
// Let's log server load when a user joins
|
||||||
let srvSockets = this.Io.sockets.sockets;
|
const srvSockets = this.Io.sockets.sockets;
|
||||||
this.nbClientsGauge.inc({ host: os.hostname() });
|
this.nbClientsGauge.inc({ host: os.hostname() });
|
||||||
console.log(new Date().toISOString() + ' A user joined (', Object.keys(srvSockets).length, ' connected users)');
|
console.log(new Date().toISOString() + ' A user joined (', Object.keys(srvSockets).length, ' connected users)');
|
||||||
si.currentLoad().then(data => console.log(' Current load: ', data.avgload));
|
si.currentLoad().then(data => console.log(' Current load: ', data.avgload));
|
||||||
@ -131,21 +153,16 @@ export class IoSocketController {
|
|||||||
x: user x position on map
|
x: user x position on map
|
||||||
y: user y position on map
|
y: user y position on map
|
||||||
*/
|
*/
|
||||||
socket.on(SockerIoEvent.JOIN_ROOM, (message: any, answerFn): void => {
|
socket.on(SockerIoEvent.JOIN_ROOM, (message: unknown, answerFn): void => {
|
||||||
try {
|
try {
|
||||||
let roomId = message.roomId;
|
if (!isJoinRoomMessageInterface(message)) {
|
||||||
|
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid JOIN_ROOM message.'});
|
||||||
if (typeof(roomId) !== 'string') {
|
console.warn('Invalid JOIN_ROOM message received: ', message);
|
||||||
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Expected roomId as a string.'});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let position = this.hydratePositionReceive(message.position);
|
|
||||||
if (position instanceof Error) {
|
|
||||||
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: position.message});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const roomId = message.roomId;
|
||||||
|
|
||||||
let Client = (socket as ExSocketInterface);
|
const Client = (socket as ExSocketInterface);
|
||||||
|
|
||||||
if (Client.roomId === roomId) {
|
if (Client.roomId === roomId) {
|
||||||
return;
|
return;
|
||||||
@ -155,18 +172,18 @@ export class IoSocketController {
|
|||||||
this.leaveRoom(Client);
|
this.leaveRoom(Client);
|
||||||
|
|
||||||
//join new previous room
|
//join new previous room
|
||||||
let world = this.joinRoom(Client, roomId, position);
|
const world = this.joinRoom(Client, roomId, message.position);
|
||||||
|
|
||||||
//add function to refresh position user in real time.
|
//add function to refresh position user in real time.
|
||||||
//this.refreshUserPosition(Client);
|
//this.refreshUserPosition(Client);
|
||||||
|
|
||||||
let messageUserJoined = new MessageUserJoined(Client.userId, Client.name, Client.character, Client.position);
|
const messageUserJoined = new MessageUserJoined(Client.userId, Client.name, Client.character, Client.position);
|
||||||
|
|
||||||
socket.to(roomId).emit(SockerIoEvent.JOIN_ROOM, messageUserJoined);
|
socket.to(roomId).emit(SockerIoEvent.JOIN_ROOM, messageUserJoined);
|
||||||
|
|
||||||
// The answer shall contain the list of all users of the room with their positions:
|
// The answer shall contain the list of all users of the room with their positions:
|
||||||
let listOfUsers = Array.from(world.getUsers(), ([key, user]) => {
|
const listOfUsers = Array.from(world.getUsers(), ([key, user]) => {
|
||||||
let player = this.searchClientByIdOrFail(user.id);
|
const player = this.searchClientByIdOrFail(user.id);
|
||||||
return new MessageUserPosition(user.id, player.name, player.character, player.position);
|
return new MessageUserPosition(user.id, player.name, player.character, player.position);
|
||||||
});
|
});
|
||||||
answerFn(listOfUsers);
|
answerFn(listOfUsers);
|
||||||
@ -176,21 +193,21 @@ export class IoSocketController {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on(SockerIoEvent.USER_POSITION, (message: any): void => {
|
socket.on(SockerIoEvent.USER_POSITION, (position: unknown): void => {
|
||||||
try {
|
try {
|
||||||
let position = this.hydratePositionReceive(message);
|
if (!isPointInterface(position)) {
|
||||||
if (position instanceof Error) {
|
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid USER_POSITION message.'});
|
||||||
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: position.message});
|
console.warn('Invalid USER_POSITION message received: ', position);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Client = (socket as ExSocketInterface);
|
const Client = (socket as ExSocketInterface);
|
||||||
|
|
||||||
// sending to all clients in room except sender
|
// sending to all clients in room except sender
|
||||||
Client.position = position;
|
Client.position = position;
|
||||||
|
|
||||||
// update position in the world
|
// update position in the world
|
||||||
let world = this.Worlds.get(Client.roomId);
|
const world = this.Worlds.get(Client.roomId);
|
||||||
if (!world) {
|
if (!world) {
|
||||||
console.error("Could not find world with id '", Client.roomId, "'");
|
console.error("Could not find world with id '", Client.roomId, "'");
|
||||||
return;
|
return;
|
||||||
@ -204,9 +221,14 @@ export class IoSocketController {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on(SockerIoEvent.WEBRTC_SIGNAL, (data: any) => {
|
socket.on(SockerIoEvent.WEBRTC_SIGNAL, (data: unknown) => {
|
||||||
|
if (!isWebRtcSignalMessageInterface(data)) {
|
||||||
|
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid WEBRTC_SIGNAL message.'});
|
||||||
|
console.warn('Invalid WEBRTC_SIGNAL message received: ', data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
//send only at user
|
//send only at user
|
||||||
let client = this.sockets.get(data.receiverId);
|
const client = this.sockets.get(data.receiverId);
|
||||||
if (client === undefined) {
|
if (client === undefined) {
|
||||||
console.warn("While exchanging a WebRTC signal: client with id ", data.receiverId, " does not exist. This might be a race condition.");
|
console.warn("While exchanging a WebRTC signal: client with id ", data.receiverId, " does not exist. This might be a race condition.");
|
||||||
return;
|
return;
|
||||||
@ -214,18 +236,8 @@ export class IoSocketController {
|
|||||||
return client.emit(SockerIoEvent.WEBRTC_SIGNAL, data);
|
return client.emit(SockerIoEvent.WEBRTC_SIGNAL, data);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on(SockerIoEvent.WEBRTC_OFFER, (data: any) => {
|
|
||||||
//send only at user
|
|
||||||
let client = this.sockets.get(data.receiverId);
|
|
||||||
if (client === undefined) {
|
|
||||||
console.warn("While exchanging a WebRTC offer: client with id ", data.receiverId, " does not exist. This might be a race condition.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
client.emit(SockerIoEvent.WEBRTC_OFFER, data);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on(SockerIoEvent.DISCONNECT, () => {
|
socket.on(SockerIoEvent.DISCONNECT, () => {
|
||||||
let Client = (socket as ExSocketInterface);
|
const Client = (socket as ExSocketInterface);
|
||||||
try {
|
try {
|
||||||
//leave room
|
//leave room
|
||||||
this.leaveRoom(Client);
|
this.leaveRoom(Client);
|
||||||
@ -245,7 +257,7 @@ export class IoSocketController {
|
|||||||
this.sockets.delete(Client.userId);
|
this.sockets.delete(Client.userId);
|
||||||
|
|
||||||
// Let's log server load when a user leaves
|
// Let's log server load when a user leaves
|
||||||
let srvSockets = this.Io.sockets.sockets;
|
const srvSockets = this.Io.sockets.sockets;
|
||||||
this.nbClientsGauge.dec({ host: os.hostname() });
|
this.nbClientsGauge.dec({ host: os.hostname() });
|
||||||
console.log('A user left (', Object.keys(srvSockets).length, ' connected users)');
|
console.log('A user left (', Object.keys(srvSockets).length, ' connected users)');
|
||||||
si.currentLoad().then(data => console.log('Current load: ', data.avgload));
|
si.currentLoad().then(data => console.log('Current load: ', data.avgload));
|
||||||
@ -254,8 +266,13 @@ export class IoSocketController {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Let's send the user id to the user
|
// Let's send the user id to the user
|
||||||
socket.on(SockerIoEvent.SET_PLAYER_DETAILS, (playerDetails: SetPlayerDetailsMessage, answerFn) => {
|
socket.on(SockerIoEvent.SET_PLAYER_DETAILS, (playerDetails: unknown, answerFn) => {
|
||||||
let Client = (socket as ExSocketInterface);
|
if (!isSetPlayerDetailsMessage(playerDetails)) {
|
||||||
|
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_PLAYER_DETAILS message.'});
|
||||||
|
console.warn('Invalid SET_PLAYER_DETAILS message received: ', playerDetails);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const Client = (socket as ExSocketInterface);
|
||||||
Client.name = playerDetails.name;
|
Client.name = playerDetails.name;
|
||||||
Client.character = playerDetails.character;
|
Client.character = playerDetails.character;
|
||||||
answerFn(Client.userId);
|
answerFn(Client.userId);
|
||||||
@ -264,7 +281,7 @@ export class IoSocketController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
searchClientByIdOrFail(userId: string): ExSocketInterface {
|
searchClientByIdOrFail(userId: string): ExSocketInterface {
|
||||||
let client: ExSocketInterface|undefined = this.sockets.get(userId);
|
const client: ExSocketInterface|undefined = this.sockets.get(userId);
|
||||||
if (client === undefined) {
|
if (client === undefined) {
|
||||||
throw new Error("Could not find user with id " + userId);
|
throw new Error("Could not find user with id " + userId);
|
||||||
}
|
}
|
||||||
@ -277,7 +294,7 @@ export class IoSocketController {
|
|||||||
Client.to(Client.roomId).emit(SockerIoEvent.USER_LEFT, Client.userId);
|
Client.to(Client.roomId).emit(SockerIoEvent.USER_LEFT, Client.userId);
|
||||||
|
|
||||||
//user leave previous world
|
//user leave previous world
|
||||||
let world : World|undefined = this.Worlds.get(Client.roomId);
|
const world : World|undefined = this.Worlds.get(Client.roomId);
|
||||||
if(world){
|
if(world){
|
||||||
world.leave(Client);
|
world.leave(Client);
|
||||||
}
|
}
|
||||||
@ -288,7 +305,7 @@ export class IoSocketController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private joinRoom(Client : ExSocketInterface, roomId: string, position: Point): World {
|
private joinRoom(Client : ExSocketInterface, roomId: string, position: PointInterface): World {
|
||||||
//join user in room
|
//join user in room
|
||||||
Client.join(roomId);
|
Client.join(roomId);
|
||||||
this.nbClientsPerRoomGauge.inc({ host: os.hostname(), room: roomId });
|
this.nbClientsPerRoomGauge.inc({ host: os.hostname(), room: roomId });
|
||||||
@ -337,13 +354,13 @@ export class IoSocketController {
|
|||||||
if (this.Io.sockets.adapter.rooms[roomId].length < 2 /*|| this.Io.sockets.adapter.rooms[roomId].length >= 4*/) {
|
if (this.Io.sockets.adapter.rooms[roomId].length < 2 /*|| this.Io.sockets.adapter.rooms[roomId].length >= 4*/) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let clients: Array<ExSocketInterface> = (Object.values(this.Io.sockets.sockets) as Array<ExSocketInterface>)
|
const clients: Array<ExSocketInterface> = (Object.values(this.Io.sockets.sockets) as Array<ExSocketInterface>)
|
||||||
.filter((client: ExSocketInterface) => client.webRtcRoomId && client.webRtcRoomId === roomId);
|
.filter((client: ExSocketInterface) => client.webRtcRoomId && client.webRtcRoomId === roomId);
|
||||||
//send start at one client to initialise offer webrtc
|
//send start at one client to initialise offer webrtc
|
||||||
//send all users in room to create PeerConnection in front
|
//send all users in room to create PeerConnection in front
|
||||||
clients.forEach((client: ExSocketInterface, index: number) => {
|
clients.forEach((client: ExSocketInterface, index: number) => {
|
||||||
|
|
||||||
let clientsId = clients.reduce((tabs: Array<any>, clientId: ExSocketInterface, indexClientId: number) => {
|
const clientsId = clients.reduce((tabs: Array<UserInGroupInterface>, clientId: ExSocketInterface, indexClientId: number) => {
|
||||||
if (!clientId.userId || clientId.userId === client.userId) {
|
if (!clientId.userId || clientId.userId === client.userId) {
|
||||||
return tabs;
|
return tabs;
|
||||||
}
|
}
|
||||||
@ -359,19 +376,6 @@ export class IoSocketController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//Hydrate and manage error
|
|
||||||
hydratePositionReceive(message: any): Point | Error {
|
|
||||||
try {
|
|
||||||
if (!message.x || !message.y || !message.direction || message.moving === undefined) {
|
|
||||||
return new Error("invalid point message sent");
|
|
||||||
}
|
|
||||||
return new Point(message.x, message.y, message.direction, message.moving);
|
|
||||||
} catch (err) {
|
|
||||||
//TODO log error
|
|
||||||
return new Error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** permit to share user position
|
/** permit to share user position
|
||||||
** users position will send in event 'user-position'
|
** users position will send in event 'user-position'
|
||||||
** The data sent is an array with information for each user :
|
** The data sent is an array with information for each user :
|
||||||
@ -395,13 +399,13 @@ export class IoSocketController {
|
|||||||
if (Client === undefined) {
|
if (Client === undefined) {
|
||||||
return;
|
return;
|
||||||
}*/
|
}*/
|
||||||
let Client = this.searchClientByIdOrFail(userId);
|
const Client = this.searchClientByIdOrFail(userId);
|
||||||
this.joinWebRtcRoom(Client, group.getId());
|
this.joinWebRtcRoom(Client, group.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
//disconnect user
|
//disconnect user
|
||||||
disConnectedUser(userId: string, group: Group) {
|
disConnectedUser(userId: string, group: Group) {
|
||||||
let Client = this.searchClientByIdOrFail(userId);
|
const Client = this.searchClientByIdOrFail(userId);
|
||||||
Client.to(group.getId()).emit(SockerIoEvent.WEBRTC_DISCONNECT, {
|
Client.to(group.getId()).emit(SockerIoEvent.WEBRTC_DISCONNECT, {
|
||||||
userId: userId
|
userId: userId
|
||||||
});
|
});
|
||||||
@ -411,7 +415,7 @@ export class IoSocketController {
|
|||||||
// However! In the rare case where the WebRTC connection is not yet established, if we close the connection on one of the player,
|
// However! In the rare case where the WebRTC connection is not yet established, if we close the connection on one of the player,
|
||||||
// the other player will try connecting until a timeout happens (during this time, the connection icon will be displayed for nothing).
|
// the other player will try connecting until a timeout happens (during this time, the connection icon will be displayed for nothing).
|
||||||
// So we also send the disconnect event to the other player.
|
// So we also send the disconnect event to the other player.
|
||||||
for (let user of group.getUsers()) {
|
for (const user of group.getUsers()) {
|
||||||
Client.emit(SockerIoEvent.WEBRTC_DISCONNECT, {
|
Client.emit(SockerIoEvent.WEBRTC_DISCONNECT, {
|
||||||
userId: user.id
|
userId: user.id
|
||||||
});
|
});
|
||||||
|
@ -68,7 +68,7 @@ export class Group {
|
|||||||
|
|
||||||
isPartOfGroup(user: UserInterface): boolean
|
isPartOfGroup(user: UserInterface): boolean
|
||||||
{
|
{
|
||||||
return this.users.indexOf(user) !== -1;
|
return this.users.includes(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*removeFromGroup(users: UserInterface[]): void
|
/*removeFromGroup(users: UserInterface[]): void
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import {Socket} from "socket.io";
|
import {Socket} from "socket.io";
|
||||||
import {PointInterface} from "./PointInterface";
|
import {PointInterface} from "./PointInterface";
|
||||||
import {Identificable} from "./Identificable";
|
import {Identificable} from "./Identificable";
|
||||||
|
import {TokenInterface} from "../../Controller/AuthenticateController";
|
||||||
|
|
||||||
export interface ExSocketInterface extends Socket, Identificable {
|
export interface ExSocketInterface extends Socket, Identificable {
|
||||||
token: any;
|
token: string;
|
||||||
roomId: string;
|
roomId: string;
|
||||||
webRtcRoomId: string;
|
webRtcRoomId: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
|
9
back/src/Model/Websocket/JoinRoomMessage.ts
Normal file
9
back/src/Model/Websocket/JoinRoomMessage.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
import {isPointInterface} from "./PointInterface";
|
||||||
|
|
||||||
|
export const isJoinRoomMessageInterface =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
roomId: tg.isString,
|
||||||
|
position: isPointInterface,
|
||||||
|
}).get();
|
||||||
|
export type JoinRoomMessageInterface = tg.GuardedType<typeof isJoinRoomMessageInterface>;
|
@ -1,4 +1,3 @@
|
|||||||
import {Point} from "./MessageUserPosition";
|
|
||||||
import {PointInterface} from "_Model/Websocket/PointInterface";
|
import {PointInterface} from "_Model/Websocket/PointInterface";
|
||||||
|
|
||||||
export class MessageUserJoined {
|
export class MessageUserJoined {
|
||||||
|
@ -1,5 +1,17 @@
|
|||||||
export interface PointInterface {
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
/*export interface PointInterface {
|
||||||
readonly x: number;
|
readonly x: number;
|
||||||
readonly y: number;
|
readonly y: number;
|
||||||
readonly direction: string;
|
readonly direction: string;
|
||||||
}
|
readonly moving: boolean;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
export const isPointInterface =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
x: tg.isNumber,
|
||||||
|
y: tg.isNumber,
|
||||||
|
direction: tg.isString,
|
||||||
|
moving: tg.isBoolean
|
||||||
|
}).get();
|
||||||
|
export type PointInterface = tg.GuardedType<typeof isPointInterface>;
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
export interface SetPlayerDetailsMessage {
|
import * as tg from "generic-type-guard";
|
||||||
name: string,
|
|
||||||
character: string
|
export const isSetPlayerDetailsMessage =
|
||||||
}
|
new tg.IsInterface().withProperties({
|
||||||
|
name: tg.isString,
|
||||||
|
character: tg.isString
|
||||||
|
}).get();
|
||||||
|
export type SetPlayerDetailsMessage = tg.GuardedType<typeof isSetPlayerDetailsMessage>;
|
||||||
|
5
back/src/Model/Websocket/UserInGroupInterface.ts
Normal file
5
back/src/Model/Websocket/UserInGroupInterface.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export interface UserInGroupInterface {
|
||||||
|
userId: string,
|
||||||
|
name: string,
|
||||||
|
initiator: boolean
|
||||||
|
}
|
10
back/src/Model/Websocket/WebRtcSignalMessage.ts
Normal file
10
back/src/Model/Websocket/WebRtcSignalMessage.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isWebRtcSignalMessageInterface =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
userId: tg.isString,
|
||||||
|
receiverId: tg.isString,
|
||||||
|
roomId: tg.isString,
|
||||||
|
signal: tg.isUnknown
|
||||||
|
}).get();
|
||||||
|
export type WebRtcSignalMessageInterface = tg.GuardedType<typeof isWebRtcSignalMessageInterface>;
|
@ -62,7 +62,7 @@ export class World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public leave(user : Identificable){
|
public leave(user : Identificable){
|
||||||
let userObj = this.users.get(user.userId);
|
const userObj = this.users.get(user.userId);
|
||||||
if (userObj === undefined) {
|
if (userObj === undefined) {
|
||||||
console.warn('User ', user.userId, 'does not belong to world! It should!');
|
console.warn('User ', user.userId, 'does not belong to world! It should!');
|
||||||
}
|
}
|
||||||
@ -73,7 +73,7 @@ export class World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public updatePosition(socket : Identificable, userPosition: PointInterface): void {
|
public updatePosition(socket : Identificable, userPosition: PointInterface): void {
|
||||||
let user = this.users.get(socket.userId);
|
const user = this.users.get(socket.userId);
|
||||||
if(typeof user === 'undefined') {
|
if(typeof user === 'undefined') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -83,15 +83,15 @@ export class World {
|
|||||||
if (typeof user.group === 'undefined') {
|
if (typeof user.group === 'undefined') {
|
||||||
// If the user is not part of a group:
|
// If the user is not part of a group:
|
||||||
// should he join a group?
|
// should he join a group?
|
||||||
let closestItem: UserInterface|Group|null = this.searchClosestAvailableUserOrGroup(user);
|
const closestItem: UserInterface|Group|null = this.searchClosestAvailableUserOrGroup(user);
|
||||||
|
|
||||||
if (closestItem !== null) {
|
if (closestItem !== null) {
|
||||||
if (closestItem instanceof Group) {
|
if (closestItem instanceof Group) {
|
||||||
// Let's join the group!
|
// Let's join the group!
|
||||||
closestItem.join(user);
|
closestItem.join(user);
|
||||||
} else {
|
} else {
|
||||||
let closestUser : UserInterface = closestItem;
|
const closestUser : UserInterface = closestItem;
|
||||||
let group: Group = new Group([
|
const group: Group = new Group([
|
||||||
user,
|
user,
|
||||||
closestUser
|
closestUser
|
||||||
], this.connectCallback, this.disconnectCallback);
|
], this.connectCallback, this.disconnectCallback);
|
||||||
@ -102,7 +102,7 @@ export class World {
|
|||||||
} else {
|
} else {
|
||||||
// If the user is part of a group:
|
// If the user is part of a group:
|
||||||
// should he leave the group?
|
// should he leave the group?
|
||||||
let distance = World.computeDistanceBetweenPositions(user.position, user.group.getPosition());
|
const distance = World.computeDistanceBetweenPositions(user.position, user.group.getPosition());
|
||||||
if (distance > this.groupRadius) {
|
if (distance > this.groupRadius) {
|
||||||
this.leaveGroup(user);
|
this.leaveGroup(user);
|
||||||
}
|
}
|
||||||
@ -120,7 +120,7 @@ export class World {
|
|||||||
* @param user
|
* @param user
|
||||||
*/
|
*/
|
||||||
private leaveGroup(user: UserInterface): void {
|
private leaveGroup(user: UserInterface): void {
|
||||||
let group = user.group;
|
const group = user.group;
|
||||||
if (typeof group === 'undefined') {
|
if (typeof group === 'undefined') {
|
||||||
throw new Error("The user is part of no group");
|
throw new Error("The user is part of no group");
|
||||||
}
|
}
|
||||||
@ -158,7 +158,7 @@ export class World {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let distance = World.computeDistance(user, currentUser); // compute distance between peers.
|
const distance = World.computeDistance(user, currentUser); // compute distance between peers.
|
||||||
|
|
||||||
if(distance <= minimumDistanceFound && distance <= this.minDistance) {
|
if(distance <= minimumDistanceFound && distance <= this.minDistance) {
|
||||||
minimumDistanceFound = distance;
|
minimumDistanceFound = distance;
|
||||||
@ -204,7 +204,7 @@ export class World {
|
|||||||
if (group.isFull()) {
|
if (group.isFull()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let distance = World.computeDistanceBetweenPositions(user.position, group.getPosition());
|
const distance = World.computeDistanceBetweenPositions(user.position, group.getPosition());
|
||||||
if(distance <= minimumDistanceFound && distance <= this.groupRadius) {
|
if(distance <= minimumDistanceFound && distance <= this.groupRadius) {
|
||||||
minimumDistanceFound = distance;
|
minimumDistanceFound = distance;
|
||||||
matchingItem = group;
|
matchingItem = group;
|
||||||
|
@ -6,14 +6,14 @@ import { Group } from "../src/Model/Group";
|
|||||||
describe("World", () => {
|
describe("World", () => {
|
||||||
it("should connect user1 and user2", () => {
|
it("should connect user1 and user2", () => {
|
||||||
let connectCalledNumber: number = 0;
|
let connectCalledNumber: number = 0;
|
||||||
let connect: ConnectCallback = (user: string, group: Group): void => {
|
const connect: ConnectCallback = (user: string, group: Group): void => {
|
||||||
connectCalledNumber++;
|
connectCalledNumber++;
|
||||||
}
|
}
|
||||||
let disconnect: DisconnectCallback = (user: string, group: Group): void => {
|
const disconnect: DisconnectCallback = (user: string, group: Group): void => {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let world = new World(connect, disconnect, 160, 160, () => {}, () => {});
|
const world = new World(connect, disconnect, 160, 160, () => {}, () => {});
|
||||||
|
|
||||||
world.join({ userId: "foo" }, new Point(100, 100));
|
world.join({ userId: "foo" }, new Point(100, 100));
|
||||||
|
|
||||||
@ -33,14 +33,14 @@ describe("World", () => {
|
|||||||
|
|
||||||
it("should connect 3 users", () => {
|
it("should connect 3 users", () => {
|
||||||
let connectCalled: boolean = false;
|
let connectCalled: boolean = false;
|
||||||
let connect: ConnectCallback = (user: string, group: Group): void => {
|
const connect: ConnectCallback = (user: string, group: Group): void => {
|
||||||
connectCalled = true;
|
connectCalled = true;
|
||||||
}
|
}
|
||||||
let disconnect: DisconnectCallback = (user: string, group: Group): void => {
|
const disconnect: DisconnectCallback = (user: string, group: Group): void => {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let world = new World(connect, disconnect, 160, 160, () => {}, () => {});
|
const world = new World(connect, disconnect, 160, 160, () => {}, () => {});
|
||||||
|
|
||||||
world.join({ userId: "foo" }, new Point(100, 100));
|
world.join({ userId: "foo" }, new Point(100, 100));
|
||||||
|
|
||||||
@ -62,14 +62,14 @@ describe("World", () => {
|
|||||||
it("should disconnect user1 and user2", () => {
|
it("should disconnect user1 and user2", () => {
|
||||||
let connectCalled: boolean = false;
|
let connectCalled: boolean = false;
|
||||||
let disconnectCallNumber: number = 0;
|
let disconnectCallNumber: number = 0;
|
||||||
let connect: ConnectCallback = (user: string, group: Group): void => {
|
const connect: ConnectCallback = (user: string, group: Group): void => {
|
||||||
connectCalled = true;
|
connectCalled = true;
|
||||||
}
|
}
|
||||||
let disconnect: DisconnectCallback = (user: string, group: Group): void => {
|
const disconnect: DisconnectCallback = (user: string, group: Group): void => {
|
||||||
disconnectCallNumber++;
|
disconnectCallNumber++;
|
||||||
}
|
}
|
||||||
|
|
||||||
let world = new World(connect, disconnect, 160, 160, () => {}, () => {});
|
const world = new World(connect, disconnect, 160, 160, () => {}, () => {});
|
||||||
|
|
||||||
world.join({ userId: "foo" }, new Point(100, 100));
|
world.join({ userId: "foo" }, new Point(100, 100));
|
||||||
|
|
||||||
|
@ -790,6 +790,11 @@ functional-red-black-tree@^1.0.1:
|
|||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
|
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
|
||||||
|
|
||||||
|
generic-type-guard@^3.2.0:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/generic-type-guard/-/generic-type-guard-3.2.0.tgz#1fb136f934730c776486526b8a21fe96b067e691"
|
||||||
|
integrity sha512-EkkrXYbOtJ3VPB+SOrU7EhwY65rZErItGtBg5wAqywaj07BOubwOZqMYaxOWekJ9akioGqXIsw1fYk3wwbWsDQ==
|
||||||
|
|
||||||
get-stdin@^4.0.1:
|
get-stdin@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
|
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
|
||||||
|
Loading…
Reference in New Issue
Block a user