Merge branch 'master' of https://github.com/thecodingmachine/workadventure into feature/back-players-proximity

This commit is contained in:
David MAECHLER 2020-04-06 17:35:58 +02:00
commit 60c0188e9e
16 changed files with 560 additions and 20 deletions

61
back/README.md Normal file
View file

@ -0,0 +1,61 @@
# Back Features
## Login
To start your game, you must authenticate on the server back.
When you are authenticated, the back server return token and room starting.
```
POST => /login
Params :
email: email of user.
```
## Join a room
When a user is connected, the user can join a room.
So you must send emit `join-room` with information user:
```
Socket.io => 'join-room'
userId: user id of gamer
roomId: room id when user enter in game
position: {
x: position x on map
y: position y on map
}
```
All data users are stocked on socket client.
## Send position user
When user move on the map, you can share new position on back with event `user-position`.
The information sent:
```
Socket.io => 'user-position'
userId: user id of gamer
roomId: room id when user enter in game
position: {
x: position x on map
y: position y on map
}
```
All data users are updated on socket client.
## Receive positions of all users
The application sends position of all users in each room in every few 10 milliseconds.
The data will pushed on event `user-position`:
```
Socket.io => 'user-position'
[
{
userId: user id of gamer
roomId: room id when user enter in game
position: {
x: position x on map
y: position y on map
}
},
...
]
```
[<<< back](../README.md)

View file

@ -1,8 +1,8 @@
// lib/app.ts
import {IoSocketController} from "./Controller/IoSocketController"; //TODO fix impot by "_Controller/..."
import {AuthenticateController} from "./Controller/AuthenticateController"; //TODO fix impot by "_Controller/..."
import {IoSocketController} from "./Controller/IoSocketController"; //TODO fix import by "_Controller/..."
import {AuthenticateController} from "./Controller/AuthenticateController"; //TODO fix import by "_Controller/..."
import express from "express";
import {Application} from 'express';
import {Application, Request, Response} from 'express';
import bodyParser = require('body-parser');
import * as http from "http";
@ -28,6 +28,11 @@ class App {
private config(): void {
this.app.use(bodyParser.json());
this.app.use(bodyParser.urlencoded({extended: false}));
this.app.use(function (req: Request, res: Response, next) {
res.header("Access-Control-Allow-Origin", "*"); // update to match the domain you will make the request from
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
}
}

View file

@ -1,7 +1,7 @@
import {Application, Request, Response} from "express";
import Jwt, {JsonWebTokenError} from "jsonwebtoken";
import Jwt from "jsonwebtoken";
import {BAD_REQUEST, OK} from "http-status-codes";
import {SECRET_KEY} from "../Enum/EnvironmentVariable";
import {SECRET_KEY, ROOM} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..."
export class AuthenticateController{
App : Application;
@ -21,8 +21,8 @@ export class AuthenticateController{
});
}
//TODO check user email for The Coding Machine game
let token = Jwt.sign({email: param.email}, SECRET_KEY, {expiresIn: '24h'});
return res.status(OK).send({token: token});
let token = Jwt.sign({email: param.email, roomId: ROOM}, SECRET_KEY, {expiresIn: '24h'});
return res.status(OK).send({token: token, roomId: ROOM});
});
}
}

View file

@ -5,6 +5,8 @@ import {MessageUserPosition} from "../Model/Websocket/MessageUserPosition"; //TO
import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.."
import Jwt, {JsonWebTokenError} from "jsonwebtoken";
import {SECRET_KEY} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..."
import {ExtRooms, RefreshUserPositionFunction} from "../Model/Websocket/ExtRoom";
import {ExtRoomsInterface} from "_Model/Websocket/ExtRoomsInterface";
export class IoSocketController{
Io: socketIO.Server;
@ -26,6 +28,7 @@ export class IoSocketController{
});
this.ioConnection();
this.shareUsersPosition();
}
ioConnection() {
@ -38,34 +41,47 @@ export class IoSocketController{
x: user x position on map
y: user y position on map
*/
socket.on('join-room', (message : string) => {
let messageUserPosition = this.hydrateMessageReceive(message);
if(messageUserPosition instanceof Error){
return socket.emit("message-error", JSON.stringify({message: messageUserPosition.message}))
}
//join user in room
socket.join(messageUserPosition.roomId);
// sending to all clients in room except sender
this.saveUserPosition((socket as ExSocketInterface), messageUserPosition);
this.saveUserInformation((socket as ExSocketInterface), messageUserPosition);
//add function to refresh position user in real time.
let rooms = (this.Io.sockets.adapter.rooms as ExtRoomsInterface);
rooms.refreshUserPosition = RefreshUserPositionFunction;
rooms.refreshUserPosition(rooms, this.Io);
socket.to(messageUserPosition.roomId).emit('join-room', messageUserPosition.toString());
});
socket.on('user-position', (message : string) => {
let messageUserPosition = this.hydrateMessageReceive(message);
if(messageUserPosition instanceof Error){
if (messageUserPosition instanceof Error) {
return socket.emit("message-error", JSON.stringify({message: messageUserPosition.message}));
}
// sending to all clients in room except sender
this.saveUserPosition((socket as ExSocketInterface), messageUserPosition);
socket.to(messageUserPosition.roomId).emit('join-room', messageUserPosition.toString());
this.saveUserInformation((socket as ExSocketInterface), messageUserPosition);
//refresh position of all user in all rooms in real time
let rooms = (this.Io.sockets.adapter.rooms as ExtRoomsInterface)
rooms.refreshUserPosition(rooms, this.Io);
});
});
}
//permit to save user position in socket
saveUserPosition(socket : ExSocketInterface, message : MessageUserPosition){
saveUserInformation(socket : ExSocketInterface, message : MessageUserPosition){
socket.position = message.position;
socket.roomId = message.roomId;
socket.userId = message.userId;
}
//Hydrate and manage error
@ -77,4 +93,42 @@ export class IoSocketController{
return new Error(err);
}
}
/** permit to share user position
** users position will send in event 'user-position'
** The data sent is an array with information for each user :
[
{
userId: <string>,
roomId: <string>,
position: {
x : <number>,
y : <number>
}
},
...
]
**/
seTimeOutInProgress : any = null;
shareUsersPosition(){
if(this.seTimeOutInProgress){
clearTimeout(this.seTimeOutInProgress);
}
//send for each room, all data of position user
let arrayMap = (this.Io.sockets.adapter.rooms as ExtRooms).userPositionMapByRoom;
if(!arrayMap){
this.seTimeOutInProgress = setTimeout(() => {
this.shareUsersPosition();
}, 10);
return;
}
arrayMap.forEach((value : any) => {
let roomId = value[0];
let data = value[1];
this.Io.in(roomId).emit('user-position', JSON.stringify(data));
});
this.seTimeOutInProgress = setTimeout(() => {
this.shareUsersPosition();
}, 10);
}
}

View file

@ -1,5 +1,7 @@
const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY";
const ROOM = process.env.ROOM || "THECODINGMACHINE";
export {
SECRET_KEY
SECRET_KEY,
ROOM
}

View file

@ -2,6 +2,8 @@ import {Socket} from "socket.io";
import {PointInterface} from "./PointInterface";
export interface ExSocketInterface extends Socket {
token: object;
token: any;
roomId: string;
userId: string;
position: PointInterface;
}

View file

@ -0,0 +1,42 @@
import {ExtRoomsInterface} from "./ExtRoomsInterface";
import socketIO = require('socket.io');
import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
export class ExtRooms implements ExtRoomsInterface{
userPositionMapByRoom: any;
refreshUserPosition: any;
[room: string]: SocketIO.Room;
}
let RefreshUserPositionFunction = function(rooms : ExtRooms, Io: socketIO.Server){
let clients = Io.clients();
let socketsKey = Object.keys(Io.clients().sockets);
//create mapping with all users in all rooms
let mapPositionUserByRoom = new Map();
for(let i = 0; i < socketsKey.length; i++){
let socket = clients.sockets[socketsKey[i]];
if(!(socket as ExSocketInterface).position){
continue;
}
let data = {
userId : (socket as ExSocketInterface).userId,
roomId : (socket as ExSocketInterface).roomId,
position : (socket as ExSocketInterface).position,
};
let dataArray = <any>[];
if(mapPositionUserByRoom.get(data.roomId)){
dataArray = mapPositionUserByRoom.get(data.roomId);
dataArray.push(data);
}else{
dataArray = [data];
}
mapPositionUserByRoom.set(data.roomId, dataArray);
}
rooms.userPositionMapByRoom = Array.from(mapPositionUserByRoom);
}
export {
RefreshUserPositionFunction
}

View file

@ -0,0 +1,6 @@
import {Rooms} from "socket.io";
export interface ExtRoomsInterface extends Rooms{
userPositionMapByRoom: any;
refreshUserPosition: any;
}

View file

@ -6,7 +6,7 @@ export class Point implements PointInterface{
y: number;
constructor(x : number, y : number) {
if(!x || !y){
if(x === null || y === null){
throw Error("position x and y cannot be null");
}
this.x = x;
@ -22,7 +22,7 @@ export class Point implements PointInterface{
}
export class MessageUserPosition extends Message{
position: PointInterface
position: PointInterface;
constructor(message: string) {
super(message);

View file

@ -44,7 +44,8 @@
"baseUrl": ".", /* Base directory to resolve non-absolute module names. */
"paths": {
"_Controller/*": ["src/Controller/*"],
"_Model/*": ["src/Model/*"]
"_Model/*": ["src/Model/*"],
"_Enum/*": ["src/Enum/*"]
}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */