Merge pull request #332 from thecodingmachine/ioSocketRefractor
move most of the logic of IOSocketController into a dedicated class
This commit is contained in:
commit
4334ab0f54
@ -24,8 +24,8 @@ class App {
|
|||||||
this.authenticateController = new AuthenticateController(this.app);
|
this.authenticateController = new AuthenticateController(this.app);
|
||||||
this.fileController = new FileController(this.app);
|
this.fileController = new FileController(this.app);
|
||||||
this.mapController = new MapController(this.app);
|
this.mapController = new MapController(this.app);
|
||||||
this.prometheusController = new PrometheusController(this.app, this.ioSocketController);
|
this.prometheusController = new PrometheusController(this.app);
|
||||||
this.debugController = new DebugController(this.app, this.ioSocketController);
|
this.debugController = new DebugController(this.app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,9 +4,10 @@ import {stringify} from "circular-json";
|
|||||||
import {HttpRequest, HttpResponse} from "uWebSockets.js";
|
import {HttpRequest, HttpResponse} from "uWebSockets.js";
|
||||||
import { parse } from 'query-string';
|
import { parse } from 'query-string';
|
||||||
import {App} from "../Server/sifrr.server";
|
import {App} from "../Server/sifrr.server";
|
||||||
|
import {socketManager} from "../Services/SocketManager";
|
||||||
|
|
||||||
export class DebugController {
|
export class DebugController {
|
||||||
constructor(private App : App, private ioSocketController: IoSocketController) {
|
constructor(private App : App) {
|
||||||
this.getDump();
|
this.getDump();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ export class DebugController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return res.writeStatus('200 OK').writeHeader('Content-Type', 'application/json').end(stringify(
|
return res.writeStatus('200 OK').writeHeader('Content-Type', 'application/json').end(stringify(
|
||||||
this.ioSocketController.getWorlds(),
|
socketManager.getWorlds(),
|
||||||
(key: unknown, value: unknown) => {
|
(key: unknown, value: unknown) => {
|
||||||
if(value instanceof Map) {
|
if(value instanceof Map) {
|
||||||
const obj: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any
|
const obj: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
@ -1,113 +1,48 @@
|
|||||||
import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.."
|
import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.."
|
||||||
import {MINIMUM_DISTANCE, GROUP_RADIUS, ADMIN_API_URL, ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..."
|
import {GameRoomPolicyTypes} from "../Model/GameRoom";
|
||||||
import {GameRoom, GameRoomPolicyTypes} from "../Model/GameRoom";
|
|
||||||
import {Group} from "../Model/Group";
|
|
||||||
import {User} from "../Model/User";
|
|
||||||
import {isSetPlayerDetailsMessage,} from "../Model/Websocket/SetPlayerDetailsMessage";
|
|
||||||
import {Gauge} from "prom-client";
|
|
||||||
import {PointInterface} from "../Model/Websocket/PointInterface";
|
import {PointInterface} from "../Model/Websocket/PointInterface";
|
||||||
import {Movable} from "../Model/Movable";
|
|
||||||
import {
|
import {
|
||||||
PositionMessage,
|
|
||||||
SetPlayerDetailsMessage,
|
SetPlayerDetailsMessage,
|
||||||
SubMessage,
|
SubMessage,
|
||||||
UserMovedMessage,
|
|
||||||
BatchMessage,
|
BatchMessage,
|
||||||
GroupUpdateMessage,
|
|
||||||
PointMessage,
|
|
||||||
GroupDeleteMessage,
|
|
||||||
UserJoinedMessage,
|
|
||||||
UserLeftMessage,
|
|
||||||
ItemEventMessage,
|
ItemEventMessage,
|
||||||
ViewportMessage,
|
ViewportMessage,
|
||||||
ClientToServerMessage,
|
ClientToServerMessage,
|
||||||
ErrorMessage,
|
|
||||||
RoomJoinedMessage,
|
|
||||||
ItemStateMessage,
|
|
||||||
ServerToClientMessage,
|
|
||||||
SilentMessage,
|
SilentMessage,
|
||||||
WebRtcSignalToClientMessage,
|
|
||||||
WebRtcSignalToServerMessage,
|
WebRtcSignalToServerMessage,
|
||||||
WebRtcStartMessage,
|
|
||||||
WebRtcDisconnectMessage,
|
|
||||||
PlayGlobalMessage,
|
PlayGlobalMessage,
|
||||||
ReportPlayerMessage
|
ReportPlayerMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import {UserMovesMessage} from "../Messages/generated/messages_pb";
|
import {UserMovesMessage} from "../Messages/generated/messages_pb";
|
||||||
import Direction = PositionMessage.Direction;
|
|
||||||
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
|
||||||
import {TemplatedApp} from "uWebSockets.js"
|
import {TemplatedApp} from "uWebSockets.js"
|
||||||
import {parse} from "query-string";
|
import {parse} from "query-string";
|
||||||
import {cpuTracker} from "../Services/CpuTracker";
|
|
||||||
import {ViewportInterface} from "../Model/Websocket/ViewportMessage";
|
|
||||||
import {jwtTokenManager} from "../Services/JWTTokenManager";
|
import {jwtTokenManager} from "../Services/JWTTokenManager";
|
||||||
import {adminApi} from "../Services/AdminApi";
|
import {adminApi} from "../Services/AdminApi";
|
||||||
import Axios from "axios";
|
import {socketManager} from "../Services/SocketManager";
|
||||||
import {PositionInterface} from "../Model/PositionInterface";
|
import {emitInBatch, resetPing} from "../Services/IoSocketHelpers";
|
||||||
|
|
||||||
function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void {
|
|
||||||
socket.batchedMessages.addPayload(payload);
|
|
||||||
|
|
||||||
if (socket.batchTimeout === null) {
|
|
||||||
socket.batchTimeout = setTimeout(() => {
|
|
||||||
if (socket.disconnecting) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const serverToClientMessage = new ServerToClientMessage();
|
|
||||||
serverToClientMessage.setBatchmessage(socket.batchedMessages);
|
|
||||||
|
|
||||||
socket.send(serverToClientMessage.serializeBinary().buffer, true);
|
|
||||||
socket.batchedMessages = new BatchMessage();
|
|
||||||
socket.batchTimeout = null;
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we send a message, we don't need to keep the connection alive
|
|
||||||
resetPing(socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Schedule a ping to keep the connection open.
|
|
||||||
* If a ping is already set, the timeout of the ping is reset.
|
|
||||||
*/
|
|
||||||
function resetPing(ws: ExSocketInterface): void {
|
|
||||||
if (ws.pingTimeout) {
|
|
||||||
clearTimeout(ws.pingTimeout);
|
|
||||||
}
|
|
||||||
ws.pingTimeout = setTimeout(() => {
|
|
||||||
if (ws.disconnecting) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ws.ping();
|
|
||||||
resetPing(ws);
|
|
||||||
}, 29000);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class IoSocketController {
|
export class IoSocketController {
|
||||||
private Worlds: Map<string, GameRoom> = new Map<string, GameRoom>();
|
|
||||||
private sockets: Map<number, ExSocketInterface> = new Map<number, ExSocketInterface>();
|
|
||||||
private nbClientsGauge: Gauge<string>;
|
|
||||||
private nbClientsPerRoomGauge: Gauge<string>;
|
|
||||||
private nextUserId: number = 1;
|
private nextUserId: number = 1;
|
||||||
|
|
||||||
constructor(private readonly app: TemplatedApp) {
|
constructor(private readonly app: TemplatedApp) {
|
||||||
|
|
||||||
this.nbClientsGauge = new Gauge({
|
|
||||||
name: 'workadventure_nb_sockets',
|
|
||||||
help: 'Number of connected sockets',
|
|
||||||
labelNames: [ ]
|
|
||||||
});
|
|
||||||
this.nbClientsPerRoomGauge = new Gauge({
|
|
||||||
name: 'workadventure_nb_clients_per_room',
|
|
||||||
help: 'Number of clients per room',
|
|
||||||
labelNames: [ 'room' ]
|
|
||||||
});
|
|
||||||
|
|
||||||
this.ioConnection();
|
this.ioConnection();
|
||||||
|
this.adminRoomSocket();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
adminRoomSocket() {
|
||||||
|
/*this.app.ws('/admin/rooms', {
|
||||||
|
open: (ws) => {
|
||||||
|
console.log('o', ws)
|
||||||
|
ws.send('Hello');
|
||||||
|
},
|
||||||
|
message: (ws, arrayBuffer, isBinary): void => {
|
||||||
|
console.log('m', ws)
|
||||||
|
},
|
||||||
|
close: (ws, code, message) => {
|
||||||
|
console.log('close');
|
||||||
|
}
|
||||||
|
})*/
|
||||||
|
}
|
||||||
|
|
||||||
ioConnection() {
|
ioConnection() {
|
||||||
this.app.ws('/room', {
|
this.app.ws('/room', {
|
||||||
@ -165,7 +100,7 @@ export class IoSocketController {
|
|||||||
const userUuid = await jwtTokenManager.getUserUuidFromToken(token);
|
const userUuid = await jwtTokenManager.getUserUuidFromToken(token);
|
||||||
|
|
||||||
let memberTags: string[] = [];
|
let memberTags: string[] = [];
|
||||||
const room = await this.getOrCreateRoom(roomId);
|
const room = await socketManager.getOrCreateRoom(roomId);
|
||||||
if (!room.anonymous && room.policyType !== GameRoomPolicyTypes.ANONYMUS_POLICY) {
|
if (!room.anonymous && room.policyType !== GameRoomPolicyTypes.ANONYMUS_POLICY) {
|
||||||
try {
|
try {
|
||||||
const userData = await adminApi.fetchMemberDataByUuid(userUuid);
|
const userData = await adminApi.fetchMemberDataByUuid(userUuid);
|
||||||
@ -229,32 +164,9 @@ export class IoSocketController {
|
|||||||
},
|
},
|
||||||
/* Handlers */
|
/* Handlers */
|
||||||
open: (ws) => {
|
open: (ws) => {
|
||||||
const client : ExSocketInterface = ws as ExSocketInterface;
|
|
||||||
client.userId = this.nextUserId;
|
|
||||||
this.nextUserId++;
|
|
||||||
client.userUuid = ws.userUuid;
|
|
||||||
client.token = ws.token;
|
|
||||||
client.batchedMessages = new BatchMessage();
|
|
||||||
client.batchTimeout = null;
|
|
||||||
client.emitInBatch = (payload: SubMessage): void => {
|
|
||||||
emitInBatch(client, payload);
|
|
||||||
}
|
|
||||||
client.disconnecting = false;
|
|
||||||
|
|
||||||
client.name = ws.name;
|
|
||||||
client.tags = ws.tags;
|
|
||||||
client.characterLayers = ws.characterLayers;
|
|
||||||
client.roomId = ws.roomId;
|
|
||||||
|
|
||||||
this.sockets.set(client.userId, client);
|
|
||||||
|
|
||||||
// Let's log server load when a user joins
|
|
||||||
this.nbClientsGauge.inc();
|
|
||||||
console.log(new Date().toISOString() + ' A user joined (', this.sockets.size, ' connected users)');
|
|
||||||
|
|
||||||
// Let's join the room
|
// Let's join the room
|
||||||
this.handleJoinRoom(client, client.position, client.viewport);
|
const client = this.initClient(ws); //todo: into the upgrade instead?
|
||||||
|
socketManager.handleJoinRoom(client);
|
||||||
resetPing(client);
|
resetPing(client);
|
||||||
},
|
},
|
||||||
message: (ws, arrayBuffer, isBinary): void => {
|
message: (ws, arrayBuffer, isBinary): void => {
|
||||||
@ -262,23 +174,23 @@ export class IoSocketController {
|
|||||||
const message = ClientToServerMessage.deserializeBinary(new Uint8Array(arrayBuffer));
|
const message = ClientToServerMessage.deserializeBinary(new Uint8Array(arrayBuffer));
|
||||||
|
|
||||||
if (message.hasViewportmessage()) {
|
if (message.hasViewportmessage()) {
|
||||||
this.handleViewport(client, message.getViewportmessage() as ViewportMessage);
|
socketManager.handleViewport(client, message.getViewportmessage() as ViewportMessage);
|
||||||
} else if (message.hasUsermovesmessage()) {
|
} else if (message.hasUsermovesmessage()) {
|
||||||
this.handleUserMovesMessage(client, message.getUsermovesmessage() as UserMovesMessage);
|
socketManager.handleUserMovesMessage(client, message.getUsermovesmessage() as UserMovesMessage);
|
||||||
} else if (message.hasSetplayerdetailsmessage()) {
|
} else if (message.hasSetplayerdetailsmessage()) {
|
||||||
this.handleSetPlayerDetails(client, message.getSetplayerdetailsmessage() as SetPlayerDetailsMessage);
|
socketManager.handleSetPlayerDetails(client, message.getSetplayerdetailsmessage() as SetPlayerDetailsMessage);
|
||||||
} else if (message.hasSilentmessage()) {
|
} else if (message.hasSilentmessage()) {
|
||||||
this.handleSilentMessage(client, message.getSilentmessage() as SilentMessage);
|
socketManager.handleSilentMessage(client, message.getSilentmessage() as SilentMessage);
|
||||||
} else if (message.hasItemeventmessage()) {
|
} else if (message.hasItemeventmessage()) {
|
||||||
this.handleItemEvent(client, message.getItemeventmessage() as ItemEventMessage);
|
socketManager.handleItemEvent(client, message.getItemeventmessage() as ItemEventMessage);
|
||||||
} else if (message.hasWebrtcsignaltoservermessage()) {
|
} else if (message.hasWebrtcsignaltoservermessage()) {
|
||||||
this.emitVideo(client, message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage);
|
socketManager.emitVideo(client, message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage);
|
||||||
} else if (message.hasWebrtcscreensharingsignaltoservermessage()) {
|
} else if (message.hasWebrtcscreensharingsignaltoservermessage()) {
|
||||||
this.emitScreenSharing(client, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage);
|
socketManager.emitScreenSharing(client, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage);
|
||||||
} else if (message.hasPlayglobalmessage()) {
|
} else if (message.hasPlayglobalmessage()) {
|
||||||
this.emitPlayGlobalMessage(client, message.getPlayglobalmessage() as PlayGlobalMessage);
|
socketManager.emitPlayGlobalMessage(client, message.getPlayglobalmessage() as PlayGlobalMessage);
|
||||||
} else if (message.hasReportplayermessage()){
|
} else if (message.hasReportplayermessage()){
|
||||||
this.handleReportMessage(client, message.getReportplayermessage() as ReportPlayerMessage);
|
socketManager.handleReportMessage(client, message.getReportplayermessage() as ReportPlayerMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ok is false if backpressure was built up, wait for drain */
|
/* Ok is false if backpressure was built up, wait for drain */
|
||||||
@ -292,622 +204,33 @@ export class IoSocketController {
|
|||||||
try {
|
try {
|
||||||
Client.disconnecting = true;
|
Client.disconnecting = true;
|
||||||
//leave room
|
//leave room
|
||||||
this.leaveRoom(Client);
|
socketManager.leaveRoom(Client);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('An error occurred on "disconnect"');
|
console.error('An error occurred on "disconnect"');
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sockets.delete(Client.userId);
|
|
||||||
|
|
||||||
// Let's log server load when a user leaves
|
|
||||||
this.nbClientsGauge.dec();
|
|
||||||
console.log('A user left (', this.sockets.size, ' connected users)');
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private emitError(Client: ExSocketInterface, message: string): void {
|
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const errorMessage = new ErrorMessage();
|
private initClient(ws: any): ExSocketInterface {
|
||||||
errorMessage.setMessage(message);
|
const client : ExSocketInterface = ws;
|
||||||
|
client.userId = this.nextUserId;
|
||||||
const serverToClientMessage = new ServerToClientMessage();
|
this.nextUserId++;
|
||||||
serverToClientMessage.setErrormessage(errorMessage);
|
client.userUuid = ws.userUuid;
|
||||||
|
client.token = ws.token;
|
||||||
if (!Client.disconnecting) {
|
client.batchedMessages = new BatchMessage();
|
||||||
Client.send(serverToClientMessage.serializeBinary().buffer, true);
|
client.batchTimeout = null;
|
||||||
|
client.emitInBatch = (payload: SubMessage): void => {
|
||||||
|
emitInBatch(client, payload);
|
||||||
}
|
}
|
||||||
console.warn(message);
|
client.disconnecting = false;
|
||||||
}
|
|
||||||
|
|
||||||
private handleJoinRoom(client: ExSocketInterface, position: PointInterface, viewport: ViewportInterface): void {
|
client.name = ws.name;
|
||||||
try {
|
client.tags = ws.tags;
|
||||||
//join new previous room
|
client.characterLayers = ws.characterLayers;
|
||||||
const gameRoom = this.joinRoom(client, position);
|
client.roomId = ws.roomId;
|
||||||
|
|
||||||
const things = gameRoom.setViewport(client, viewport);
|
|
||||||
|
|
||||||
const roomJoinedMessage = new RoomJoinedMessage();
|
|
||||||
|
|
||||||
for (const thing of things) {
|
|
||||||
if (thing instanceof User) {
|
|
||||||
const player: ExSocketInterface|undefined = this.sockets.get(thing.id);
|
|
||||||
if (player === undefined) {
|
|
||||||
console.warn('Something went wrong. The World contains a user "'+thing.id+"' but this user does not exist in the sockets list!");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userJoinedMessage = new UserJoinedMessage();
|
|
||||||
userJoinedMessage.setUserid(thing.id);
|
|
||||||
userJoinedMessage.setName(player.name);
|
|
||||||
userJoinedMessage.setCharacterlayersList(player.characterLayers);
|
|
||||||
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(player.position));
|
|
||||||
|
|
||||||
roomJoinedMessage.addUser(userJoinedMessage);
|
|
||||||
} else if (thing instanceof Group) {
|
|
||||||
const groupUpdateMessage = new GroupUpdateMessage();
|
|
||||||
groupUpdateMessage.setGroupid(thing.getId());
|
|
||||||
groupUpdateMessage.setPosition(ProtobufUtils.toPointMessage(thing.getPosition()));
|
|
||||||
|
|
||||||
roomJoinedMessage.addGroup(groupUpdateMessage);
|
|
||||||
} else {
|
|
||||||
console.error("Unexpected type for Movable returned by setViewport");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [itemId, item] of gameRoom.getItemsState().entries()) {
|
|
||||||
const itemStateMessage = new ItemStateMessage();
|
|
||||||
itemStateMessage.setItemid(itemId);
|
|
||||||
itemStateMessage.setStatejson(JSON.stringify(item));
|
|
||||||
|
|
||||||
roomJoinedMessage.addItem(itemStateMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
roomJoinedMessage.setCurrentuserid(client.userId);
|
|
||||||
|
|
||||||
const serverToClientMessage = new ServerToClientMessage();
|
|
||||||
serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage);
|
|
||||||
|
|
||||||
if (!client.disconnecting) {
|
|
||||||
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('An error occurred on "join_room" event');
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleViewport(client: ExSocketInterface, viewportMessage: ViewportMessage) {
|
|
||||||
try {
|
|
||||||
const viewport = viewportMessage.toObject();
|
|
||||||
|
|
||||||
client.viewport = viewport;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleUserMovesMessage(client: ExSocketInterface, userMovesMessage: UserMovesMessage) {
|
|
||||||
//console.log(SockerIoEvent.USER_POSITION, userMovesMessage);
|
|
||||||
try {
|
|
||||||
const userMoves = userMovesMessage.toObject();
|
|
||||||
|
|
||||||
// If CPU is high, let's drop messages of users moving (we will only dispatch the final position)
|
|
||||||
if (cpuTracker.isOverHeating() && userMoves.position?.moving === true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const position = userMoves.position;
|
|
||||||
if (position === undefined) {
|
|
||||||
throw new Error('Position not found in message');
|
|
||||||
}
|
|
||||||
const viewport = userMoves.viewport;
|
|
||||||
if (viewport === undefined) {
|
|
||||||
throw new Error('Viewport not found in message');
|
|
||||||
}
|
|
||||||
|
|
||||||
let direction: string;
|
|
||||||
switch (position.direction) {
|
|
||||||
case Direction.UP:
|
|
||||||
direction = 'up';
|
|
||||||
break;
|
|
||||||
case Direction.DOWN:
|
|
||||||
direction = 'down';
|
|
||||||
break;
|
|
||||||
case Direction.LEFT:
|
|
||||||
direction = 'left';
|
|
||||||
break;
|
|
||||||
case Direction.RIGHT:
|
|
||||||
direction = 'right';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error("Unexpected direction");
|
|
||||||
}
|
|
||||||
|
|
||||||
// sending to all clients in room except sender
|
|
||||||
client.position = {
|
|
||||||
x: position.x,
|
|
||||||
y: position.y,
|
|
||||||
direction,
|
|
||||||
moving: position.moving,
|
|
||||||
};
|
|
||||||
client.viewport = viewport;
|
|
||||||
|
|
||||||
// update position in the world
|
|
||||||
const world = this.Worlds.get(client.roomId);
|
|
||||||
if (!world) {
|
|
||||||
console.error("In USER_POSITION, could not find world with id '", client.roomId, "'");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
world.updatePosition(client, client.position);
|
|
||||||
world.setViewport(client, client.viewport);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('An error occurred on "user_position" event');
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Useless now, will be useful again if we allow editing details in game
|
|
||||||
private handleSetPlayerDetails(client: ExSocketInterface, playerDetailsMessage: SetPlayerDetailsMessage) {
|
|
||||||
const playerDetails = {
|
|
||||||
name: playerDetailsMessage.getName(),
|
|
||||||
characterLayers: playerDetailsMessage.getCharacterlayersList()
|
|
||||||
};
|
|
||||||
//console.log(SocketIoEvent.SET_PLAYER_DETAILS, playerDetails);
|
|
||||||
if (!isSetPlayerDetailsMessage(playerDetails)) {
|
|
||||||
this.emitError(client, 'Invalid SET_PLAYER_DETAILS message received: ');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
client.name = playerDetails.name;
|
|
||||||
client.characterLayers = playerDetails.characterLayers;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleSilentMessage(client: ExSocketInterface, silentMessage: SilentMessage) {
|
|
||||||
try {
|
|
||||||
// update position in the world
|
|
||||||
const world = this.Worlds.get(client.roomId);
|
|
||||||
if (!world) {
|
|
||||||
console.error("In handleSilentMessage, could not find world with id '", client.roomId, "'");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
world.setSilent(client, silentMessage.getSilent());
|
|
||||||
} catch (e) {
|
|
||||||
console.error('An error occurred on "handleSilentMessage"');
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleItemEvent(ws: ExSocketInterface, itemEventMessage: ItemEventMessage) {
|
|
||||||
const itemEvent = ProtobufUtils.toItemEvent(itemEventMessage);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const world = this.Worlds.get(ws.roomId);
|
|
||||||
if (!world) {
|
|
||||||
console.error("Could not find world with id '", ws.roomId, "'");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const subMessage = new SubMessage();
|
|
||||||
subMessage.setItemeventmessage(itemEventMessage);
|
|
||||||
|
|
||||||
// Let's send the event without using the SocketIO room.
|
|
||||||
for (const user of world.getUsers().values()) {
|
|
||||||
const client = this.searchClientByIdOrFail(user.id);
|
|
||||||
//client.emit(SocketIoEvent.ITEM_EVENT, itemEvent);
|
|
||||||
emitInBatch(client, subMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
world.setItemState(itemEvent.itemId, itemEvent.state);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('An error occurred on "item_event"');
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
if (client === undefined) {
|
|
||||||
console.warn("While exchanging a WebRTC signal: client with id ", data.getReceiverid(), " does not exist. This might be a race condition.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const webrtcSignalToClient = new WebRtcSignalToClientMessage();
|
|
||||||
webrtcSignalToClient.setUserid(socket.userId);
|
|
||||||
webrtcSignalToClient.setSignal(data.getSignal());
|
|
||||||
|
|
||||||
const serverToClientMessage = new ServerToClientMessage();
|
|
||||||
serverToClientMessage.setWebrtcsignaltoclientmessage(webrtcSignalToClient);
|
|
||||||
|
|
||||||
if (!client.disconnecting) {
|
|
||||||
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emitScreenSharing(socket: ExSocketInterface, data: WebRtcSignalToServerMessage): void {
|
|
||||||
//send only at user
|
|
||||||
const client = this.sockets.get(data.getReceiverid());
|
|
||||||
if (client === undefined) {
|
|
||||||
console.warn("While exchanging a WEBRTC_SCREEN_SHARING signal: client with id ", data.getReceiverid(), " does not exist. This might be a race condition.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const webrtcSignalToClient = new WebRtcSignalToClientMessage();
|
|
||||||
webrtcSignalToClient.setUserid(socket.userId);
|
|
||||||
webrtcSignalToClient.setSignal(data.getSignal());
|
|
||||||
|
|
||||||
const serverToClientMessage = new ServerToClientMessage();
|
|
||||||
serverToClientMessage.setWebrtcscreensharingsignaltoclientmessage(webrtcSignalToClient);
|
|
||||||
|
|
||||||
if (!client.disconnecting) {
|
|
||||||
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
searchClientByIdOrFail(userId: number): ExSocketInterface {
|
|
||||||
const client: ExSocketInterface|undefined = this.sockets.get(userId);
|
|
||||||
if (client === undefined) {
|
|
||||||
throw new Error("Could not find user with id " + userId);
|
|
||||||
}
|
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
leaveRoom(Client : ExSocketInterface){
|
|
||||||
// leave previous room and world
|
|
||||||
if(Client.roomId){
|
|
||||||
try {
|
|
||||||
//user leave previous world
|
|
||||||
const world: GameRoom | undefined = this.Worlds.get(Client.roomId);
|
|
||||||
if (world) {
|
|
||||||
world.leave(Client);
|
|
||||||
if (world.isEmpty()) {
|
|
||||||
this.Worlds.delete(Client.roomId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//user leave previous room
|
|
||||||
//Client.leave(Client.roomId);
|
|
||||||
} finally {
|
|
||||||
this.nbClientsPerRoomGauge.dec({ room: Client.roomId });
|
|
||||||
//delete Client.roomId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getOrCreateRoom(roomId: string): Promise<GameRoom> {
|
|
||||||
//check and create new world for a room
|
|
||||||
let world = this.Worlds.get(roomId)
|
|
||||||
if(world === undefined){
|
|
||||||
world = new GameRoom(
|
|
||||||
roomId,
|
|
||||||
(user: User, group: Group) => this.joinWebRtcRoom(user, group),
|
|
||||||
(user: User, group: Group) => this.disConnectedUser(user, group),
|
|
||||||
MINIMUM_DISTANCE,
|
|
||||||
GROUP_RADIUS,
|
|
||||||
(thing: Movable, listener: User) => this.onRoomEnter(thing, listener),
|
|
||||||
(thing: Movable, position:PositionInterface, listener:User) => this.onClientMove(thing, position, listener),
|
|
||||||
(thing: Movable, listener:User) => this.onClientLeave(thing, listener)
|
|
||||||
);
|
|
||||||
if (!world.anonymous) {
|
|
||||||
const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug)
|
|
||||||
world.tags = data.tags
|
|
||||||
world.policyType = Number(data.policy_type)
|
|
||||||
}
|
|
||||||
this.Worlds.set(roomId, world);
|
|
||||||
}
|
|
||||||
return Promise.resolve(world)
|
|
||||||
}
|
|
||||||
|
|
||||||
private joinRoom(client : ExSocketInterface, position: PointInterface): GameRoom {
|
|
||||||
|
|
||||||
const roomId = client.roomId;
|
|
||||||
//join user in room
|
|
||||||
this.nbClientsPerRoomGauge.inc({ room: roomId });
|
|
||||||
client.position = position;
|
|
||||||
|
|
||||||
const world = this.Worlds.get(roomId)
|
|
||||||
if(world === undefined){
|
|
||||||
throw new Error('Could not find room for ID: '+client.roomId)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch groups position to newly connected user
|
|
||||||
world.getGroups().forEach((group: Group) => {
|
|
||||||
this.emitCreateUpdateGroupEvent(client, group);
|
|
||||||
});
|
|
||||||
//join world
|
|
||||||
world.join(client, client.position);
|
|
||||||
return world;
|
|
||||||
}
|
|
||||||
|
|
||||||
private onRoomEnter(thing: Movable, listener: User) {
|
|
||||||
const clientListener = this.searchClientByIdOrFail(listener.id);
|
|
||||||
if (thing instanceof User) {
|
|
||||||
const clientUser = this.searchClientByIdOrFail(thing.id);
|
|
||||||
|
|
||||||
const userJoinedMessage = new UserJoinedMessage();
|
|
||||||
if (!Number.isInteger(clientUser.userId)) {
|
|
||||||
throw new Error('clientUser.userId is not an integer '+clientUser.userId);
|
|
||||||
}
|
|
||||||
userJoinedMessage.setUserid(clientUser.userId);
|
|
||||||
userJoinedMessage.setName(clientUser.name);
|
|
||||||
userJoinedMessage.setCharacterlayersList(clientUser.characterLayers);
|
|
||||||
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(clientUser.position));
|
|
||||||
|
|
||||||
const subMessage = new SubMessage();
|
|
||||||
subMessage.setUserjoinedmessage(userJoinedMessage);
|
|
||||||
|
|
||||||
emitInBatch(clientListener, subMessage);
|
|
||||||
} else if (thing instanceof Group) {
|
|
||||||
this.emitCreateUpdateGroupEvent(clientListener, thing);
|
|
||||||
} else {
|
|
||||||
console.error('Unexpected type for Movable.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private onClientMove(thing: Movable, position:PositionInterface, listener:User): void {
|
|
||||||
const clientListener = this.searchClientByIdOrFail(listener.id);
|
|
||||||
if (thing instanceof User) {
|
|
||||||
const clientUser = this.searchClientByIdOrFail(thing.id);
|
|
||||||
|
|
||||||
const userMovedMessage = new UserMovedMessage();
|
|
||||||
userMovedMessage.setUserid(clientUser.userId);
|
|
||||||
userMovedMessage.setPosition(ProtobufUtils.toPositionMessage(clientUser.position));
|
|
||||||
|
|
||||||
const subMessage = new SubMessage();
|
|
||||||
subMessage.setUsermovedmessage(userMovedMessage);
|
|
||||||
|
|
||||||
clientListener.emitInBatch(subMessage);
|
|
||||||
//console.log("Sending USER_MOVED event");
|
|
||||||
} else if (thing instanceof Group) {
|
|
||||||
this.emitCreateUpdateGroupEvent(clientListener, thing);
|
|
||||||
} else {
|
|
||||||
console.error('Unexpected type for Movable.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private onClientLeave(thing: Movable, listener:User) {
|
|
||||||
const clientListener = this.searchClientByIdOrFail(listener.id);
|
|
||||||
if (thing instanceof User) {
|
|
||||||
const clientUser = this.searchClientByIdOrFail(thing.id);
|
|
||||||
this.emitUserLeftEvent(clientListener, clientUser.userId);
|
|
||||||
} else if (thing instanceof Group) {
|
|
||||||
this.emitDeleteGroupEvent(clientListener, thing.getId());
|
|
||||||
} else {
|
|
||||||
console.error('Unexpected type for Movable.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private emitCreateUpdateGroupEvent(client: ExSocketInterface, group: Group): void {
|
|
||||||
const position = group.getPosition();
|
|
||||||
const pointMessage = new PointMessage();
|
|
||||||
pointMessage.setX(Math.floor(position.x));
|
|
||||||
pointMessage.setY(Math.floor(position.y));
|
|
||||||
const groupUpdateMessage = new GroupUpdateMessage();
|
|
||||||
groupUpdateMessage.setGroupid(group.getId());
|
|
||||||
groupUpdateMessage.setPosition(pointMessage);
|
|
||||||
|
|
||||||
const subMessage = new SubMessage();
|
|
||||||
subMessage.setGroupupdatemessage(groupUpdateMessage);
|
|
||||||
|
|
||||||
emitInBatch(client, subMessage);
|
|
||||||
//socket.emit(SocketIoEvent.GROUP_CREATE_UPDATE, groupUpdateMessage.serializeBinary().buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private emitDeleteGroupEvent(client: ExSocketInterface, groupId: number): void {
|
|
||||||
const groupDeleteMessage = new GroupDeleteMessage();
|
|
||||||
groupDeleteMessage.setGroupid(groupId);
|
|
||||||
|
|
||||||
const subMessage = new SubMessage();
|
|
||||||
subMessage.setGroupdeletemessage(groupDeleteMessage);
|
|
||||||
|
|
||||||
emitInBatch(client, subMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private emitUserLeftEvent(client: ExSocketInterface, userId: number): void {
|
|
||||||
const userLeftMessage = new UserLeftMessage();
|
|
||||||
userLeftMessage.setUserid(userId);
|
|
||||||
|
|
||||||
const subMessage = new SubMessage();
|
|
||||||
subMessage.setUserleftmessage(userLeftMessage);
|
|
||||||
|
|
||||||
emitInBatch(client, subMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
joinWebRtcRoom(user: User, group: Group) {
|
|
||||||
/*const roomId: string = "webrtcroom"+group.getId();
|
|
||||||
if (user.socket.webRtcRoomId === roomId) {
|
|
||||||
return;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
for (const otherUser of group.getUsers()) {
|
|
||||||
if (user === otherUser) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let's send 2 messages: one to the user joining the group and one to the other user
|
|
||||||
const webrtcStartMessage1 = new WebRtcStartMessage();
|
|
||||||
webrtcStartMessage1.setUserid(otherUser.id);
|
|
||||||
webrtcStartMessage1.setName(otherUser.socket.name);
|
|
||||||
webrtcStartMessage1.setInitiator(true);
|
|
||||||
|
|
||||||
const serverToClientMessage1 = new ServerToClientMessage();
|
|
||||||
serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1);
|
|
||||||
|
|
||||||
if (!user.socket.disconnecting) {
|
|
||||||
user.socket.send(serverToClientMessage1.serializeBinary().buffer, true);
|
|
||||||
//console.log('Sending webrtcstart initiator to '+user.socket.userId)
|
|
||||||
}
|
|
||||||
|
|
||||||
const webrtcStartMessage2 = new WebRtcStartMessage();
|
|
||||||
webrtcStartMessage2.setUserid(user.id);
|
|
||||||
webrtcStartMessage2.setName(user.socket.name);
|
|
||||||
webrtcStartMessage2.setInitiator(false);
|
|
||||||
|
|
||||||
const serverToClientMessage2 = new ServerToClientMessage();
|
|
||||||
serverToClientMessage2.setWebrtcstartmessage(webrtcStartMessage2);
|
|
||||||
|
|
||||||
if (!otherUser.socket.disconnecting) {
|
|
||||||
otherUser.socket.send(serverToClientMessage2.serializeBinary().buffer, true);
|
|
||||||
//console.log('Sending webrtcstart to '+otherUser.socket.userId)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* socket.join(roomId);
|
|
||||||
socket.webRtcRoomId = roomId;
|
|
||||||
//if two persons in room share
|
|
||||||
if (this.Io.sockets.adapter.rooms[roomId].length < 2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: scanning all sockets is maybe not the most efficient
|
|
||||||
const clients: Array<ExSocketInterface> = (Object.values(this.Io.sockets.sockets) as Array<ExSocketInterface>)
|
|
||||||
.filter((client: ExSocketInterface) => client.webRtcRoomId && client.webRtcRoomId === roomId);
|
|
||||||
//send start at one client to initialise offer webrtc
|
|
||||||
//send all users in room to create PeerConnection in front
|
|
||||||
clients.forEach((client: ExSocketInterface, index: number) => {
|
|
||||||
|
|
||||||
const peerClients = clients.reduce((tabs: Array<UserInGroupInterface>, clientId: ExSocketInterface, indexClientId: number) => {
|
|
||||||
if (!clientId.userId || clientId.userId === client.userId) {
|
|
||||||
return tabs;
|
|
||||||
}
|
|
||||||
tabs.push({
|
|
||||||
userId: clientId.userId,
|
|
||||||
name: clientId.name,
|
|
||||||
initiator: index <= indexClientId
|
|
||||||
});
|
|
||||||
return tabs;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
client.emit(SocketIoEvent.WEBRTC_START, {clients: peerClients, roomId: roomId});
|
|
||||||
});*/
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 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>,
|
|
||||||
direction: <string>
|
|
||||||
}
|
|
||||||
},
|
|
||||||
...
|
|
||||||
]
|
|
||||||
**/
|
|
||||||
|
|
||||||
//disconnect user
|
|
||||||
disConnectedUser(user: User, group: Group) {
|
|
||||||
// Most of the time, sending a disconnect event to one of the players is enough (the player will close the connection
|
|
||||||
// which will be shut for the other 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).
|
|
||||||
// So we also send the disconnect event to the other player.
|
|
||||||
for (const otherUser of group.getUsers()) {
|
|
||||||
if (user === otherUser) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const webrtcDisconnectMessage1 = new WebRtcDisconnectMessage();
|
|
||||||
webrtcDisconnectMessage1.setUserid(user.id);
|
|
||||||
|
|
||||||
const serverToClientMessage1 = new ServerToClientMessage();
|
|
||||||
serverToClientMessage1.setWebrtcdisconnectmessage(webrtcDisconnectMessage1);
|
|
||||||
|
|
||||||
if (!otherUser.socket.disconnecting) {
|
|
||||||
otherUser.socket.send(serverToClientMessage1.serializeBinary().buffer, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const webrtcDisconnectMessage2 = new WebRtcDisconnectMessage();
|
|
||||||
webrtcDisconnectMessage2.setUserid(otherUser.id);
|
|
||||||
|
|
||||||
const serverToClientMessage2 = new ServerToClientMessage();
|
|
||||||
serverToClientMessage2.setWebrtcdisconnectmessage(webrtcDisconnectMessage2);
|
|
||||||
|
|
||||||
if (!user.socket.disconnecting) {
|
|
||||||
user.socket.send(serverToClientMessage2.serializeBinary().buffer, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//disconnect webrtc room
|
|
||||||
/*if(!Client.webRtcRoomId){
|
|
||||||
return;
|
|
||||||
}*/
|
|
||||||
//Client.leave(Client.webRtcRoomId);
|
|
||||||
//delete Client.webRtcRoomId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private emitPlayGlobalMessage(client: ExSocketInterface, playglobalmessage: PlayGlobalMessage) {
|
|
||||||
try {
|
|
||||||
const world = this.Worlds.get(client.roomId);
|
|
||||||
if (!world) {
|
|
||||||
console.error("In emitPlayGlobalMessage, could not find world with id '", client.roomId, "'");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const serverToClientMessage = new ServerToClientMessage();
|
|
||||||
serverToClientMessage.setPlayglobalmessage(playglobalmessage);
|
|
||||||
|
|
||||||
for (const [id, user] of world.getUsers().entries()) {
|
|
||||||
user.socket.send(serverToClientMessage.serializeBinary().buffer, true);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('An error occurred on "emitPlayGlobalMessage" event');
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public getWorlds(): Map<string, GameRoom> {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ const register = require('prom-client').register;
|
|||||||
const collectDefaultMetrics = require('prom-client').collectDefaultMetrics;
|
const collectDefaultMetrics = require('prom-client').collectDefaultMetrics;
|
||||||
|
|
||||||
export class PrometheusController {
|
export class PrometheusController {
|
||||||
constructor(private App: App, private ioSocketController: IoSocketController) {
|
constructor(private App: App) {
|
||||||
collectDefaultMetrics({
|
collectDefaultMetrics({
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
gcDurationBuckets: [0.001, 0.01, 0.1, 1, 2, 5], // These are the default buckets.
|
gcDurationBuckets: [0.001, 0.01, 0.1, 1, 2, 5], // These are the default buckets.
|
||||||
|
@ -11,11 +11,6 @@ export interface AdminApiData {
|
|||||||
userUuid: string
|
userUuid: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GrantedApiData {
|
|
||||||
granted: boolean,
|
|
||||||
memberTags: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface fetchMemberDataByUuidResponse {
|
export interface fetchMemberDataByUuidResponse {
|
||||||
uuid: string;
|
uuid: string;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
@ -66,6 +61,17 @@ class AdminApi {
|
|||||||
)
|
)
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string) {
|
||||||
|
return Axios.post(`${ADMIN_API_URL}/api/report`, {
|
||||||
|
reportedUserUuid,
|
||||||
|
reportedUserComment,
|
||||||
|
reporterUserUuid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {"Authorization": `${ADMIN_API_TOKEN}`}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const adminApi = new AdminApi();
|
export const adminApi = new AdminApi();
|
||||||
|
50
back/src/Services/IoSocketHelpers.ts
Normal file
50
back/src/Services/IoSocketHelpers.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
|
||||||
|
import {BatchMessage, ErrorMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb";
|
||||||
|
|
||||||
|
export function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void {
|
||||||
|
socket.batchedMessages.addPayload(payload);
|
||||||
|
|
||||||
|
if (socket.batchTimeout === null) {
|
||||||
|
socket.batchTimeout = setTimeout(() => {
|
||||||
|
if (socket.disconnecting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
|
serverToClientMessage.setBatchmessage(socket.batchedMessages);
|
||||||
|
|
||||||
|
socket.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||||
|
socket.batchedMessages = new BatchMessage();
|
||||||
|
socket.batchTimeout = null;
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we send a message, we don't need to keep the connection alive
|
||||||
|
resetPing(socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetPing(ws: ExSocketInterface): void {
|
||||||
|
if (ws.pingTimeout) {
|
||||||
|
clearTimeout(ws.pingTimeout);
|
||||||
|
}
|
||||||
|
ws.pingTimeout = setTimeout(() => {
|
||||||
|
if (ws.disconnecting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ws.ping();
|
||||||
|
resetPing(ws);
|
||||||
|
}, 29000);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function emitError(Client: ExSocketInterface, message: string): void {
|
||||||
|
const errorMessage = new ErrorMessage();
|
||||||
|
errorMessage.setMessage(message);
|
||||||
|
|
||||||
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
|
serverToClientMessage.setErrormessage(errorMessage);
|
||||||
|
|
||||||
|
if (!Client.disconnecting) {
|
||||||
|
Client.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||||
|
}
|
||||||
|
console.warn(message);
|
||||||
|
}
|
598
back/src/Services/SocketManager.ts
Normal file
598
back/src/Services/SocketManager.ts
Normal file
@ -0,0 +1,598 @@
|
|||||||
|
import {GameRoom} from "../Model/GameRoom";
|
||||||
|
import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface";
|
||||||
|
import {
|
||||||
|
GroupDeleteMessage,
|
||||||
|
GroupUpdateMessage,
|
||||||
|
ItemEventMessage,
|
||||||
|
ItemStateMessage,
|
||||||
|
PlayGlobalMessage,
|
||||||
|
PointMessage,
|
||||||
|
PositionMessage,
|
||||||
|
RoomJoinedMessage,
|
||||||
|
ServerToClientMessage,
|
||||||
|
SetPlayerDetailsMessage,
|
||||||
|
SilentMessage,
|
||||||
|
SubMessage,
|
||||||
|
ReportPlayerMessage,
|
||||||
|
UserJoinedMessage, UserLeftMessage,
|
||||||
|
UserMovedMessage,
|
||||||
|
UserMovesMessage,
|
||||||
|
ViewportMessage, WebRtcDisconnectMessage,
|
||||||
|
WebRtcSignalToClientMessage,
|
||||||
|
WebRtcSignalToServerMessage, WebRtcStartMessage
|
||||||
|
} from "../Messages/generated/messages_pb";
|
||||||
|
import {PointInterface} from "../Model/Websocket/PointInterface";
|
||||||
|
import {User} from "../Model/User";
|
||||||
|
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
||||||
|
import {Group} from "../Model/Group";
|
||||||
|
import {cpuTracker} from "./CpuTracker";
|
||||||
|
import {isSetPlayerDetailsMessage} from "../Model/Websocket/SetPlayerDetailsMessage";
|
||||||
|
import {GROUP_RADIUS, MINIMUM_DISTANCE} from "../Enum/EnvironmentVariable";
|
||||||
|
import {Movable} from "../Model/Movable";
|
||||||
|
import {PositionInterface} from "../Model/PositionInterface";
|
||||||
|
import {adminApi} from "./AdminApi";
|
||||||
|
import Direction = PositionMessage.Direction;
|
||||||
|
import {Gauge} from "prom-client";
|
||||||
|
import {emitError, emitInBatch} from "./IoSocketHelpers";
|
||||||
|
|
||||||
|
class SocketManager {
|
||||||
|
private Worlds: Map<string, GameRoom> = new Map<string, GameRoom>();
|
||||||
|
private sockets: Map<number, ExSocketInterface> = new Map<number, ExSocketInterface>();
|
||||||
|
private nbClientsGauge: Gauge<string>;
|
||||||
|
private nbClientsPerRoomGauge: Gauge<string>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.nbClientsGauge = new Gauge({
|
||||||
|
name: 'workadventure_nb_sockets',
|
||||||
|
help: 'Number of connected sockets',
|
||||||
|
labelNames: [ ]
|
||||||
|
});
|
||||||
|
this.nbClientsPerRoomGauge = new Gauge({
|
||||||
|
name: 'workadventure_nb_clients_per_room',
|
||||||
|
help: 'Number of clients per room',
|
||||||
|
labelNames: [ 'room' ]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleJoinRoom(client: ExSocketInterface): void {
|
||||||
|
const position = client.position;
|
||||||
|
const viewport = client.viewport;
|
||||||
|
try {
|
||||||
|
this.sockets.set(client.userId, client); //todo: should this be at the end of the function?
|
||||||
|
this.nbClientsGauge.inc();
|
||||||
|
// Let's log server load when a user joins
|
||||||
|
console.log(new Date().toISOString() + ' A user joined (', socketManager.sockets.size, ' connected users)');
|
||||||
|
|
||||||
|
//join new previous room
|
||||||
|
const gameRoom = this.joinRoom(client, position);
|
||||||
|
|
||||||
|
const things = gameRoom.setViewport(client, viewport);
|
||||||
|
|
||||||
|
const roomJoinedMessage = new RoomJoinedMessage();
|
||||||
|
|
||||||
|
for (const thing of things) {
|
||||||
|
if (thing instanceof User) {
|
||||||
|
const player: ExSocketInterface|undefined = this.sockets.get(thing.id);
|
||||||
|
if (player === undefined) {
|
||||||
|
console.warn('Something went wrong. The World contains a user "'+thing.id+"' but this user does not exist in the sockets list!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userJoinedMessage = new UserJoinedMessage();
|
||||||
|
userJoinedMessage.setUserid(thing.id);
|
||||||
|
userJoinedMessage.setName(player.name);
|
||||||
|
userJoinedMessage.setCharacterlayersList(player.characterLayers);
|
||||||
|
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(player.position));
|
||||||
|
|
||||||
|
roomJoinedMessage.addUser(userJoinedMessage);
|
||||||
|
} else if (thing instanceof Group) {
|
||||||
|
const groupUpdateMessage = new GroupUpdateMessage();
|
||||||
|
groupUpdateMessage.setGroupid(thing.getId());
|
||||||
|
groupUpdateMessage.setPosition(ProtobufUtils.toPointMessage(thing.getPosition()));
|
||||||
|
|
||||||
|
roomJoinedMessage.addGroup(groupUpdateMessage);
|
||||||
|
} else {
|
||||||
|
console.error("Unexpected type for Movable returned by setViewport");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [itemId, item] of gameRoom.getItemsState().entries()) {
|
||||||
|
const itemStateMessage = new ItemStateMessage();
|
||||||
|
itemStateMessage.setItemid(itemId);
|
||||||
|
itemStateMessage.setStatejson(JSON.stringify(item));
|
||||||
|
|
||||||
|
roomJoinedMessage.addItem(itemStateMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
roomJoinedMessage.setCurrentuserid(client.userId);
|
||||||
|
|
||||||
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
|
serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage);
|
||||||
|
|
||||||
|
if (!client.disconnecting) {
|
||||||
|
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('An error occurred on "join_room" event');
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleViewport(client: ExSocketInterface, viewportMessage: ViewportMessage) {
|
||||||
|
try {
|
||||||
|
const viewport = viewportMessage.toObject();
|
||||||
|
|
||||||
|
client.viewport = viewport;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUserMovesMessage(client: ExSocketInterface, userMovesMessage: UserMovesMessage) {
|
||||||
|
try {
|
||||||
|
const userMoves = userMovesMessage.toObject();
|
||||||
|
|
||||||
|
// If CPU is high, let's drop messages of users moving (we will only dispatch the final position)
|
||||||
|
if (cpuTracker.isOverHeating() && userMoves.position?.moving === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const position = userMoves.position;
|
||||||
|
if (position === undefined) {
|
||||||
|
throw new Error('Position not found in message');
|
||||||
|
}
|
||||||
|
const viewport = userMoves.viewport;
|
||||||
|
if (viewport === undefined) {
|
||||||
|
throw new Error('Viewport not found in message');
|
||||||
|
}
|
||||||
|
|
||||||
|
let direction: string;
|
||||||
|
switch (position.direction) {
|
||||||
|
case Direction.UP:
|
||||||
|
direction = 'up';
|
||||||
|
break;
|
||||||
|
case Direction.DOWN:
|
||||||
|
direction = 'down';
|
||||||
|
break;
|
||||||
|
case Direction.LEFT:
|
||||||
|
direction = 'left';
|
||||||
|
break;
|
||||||
|
case Direction.RIGHT:
|
||||||
|
direction = 'right';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error("Unexpected direction");
|
||||||
|
}
|
||||||
|
|
||||||
|
// sending to all clients in room except sender
|
||||||
|
client.position = {
|
||||||
|
x: position.x,
|
||||||
|
y: position.y,
|
||||||
|
direction,
|
||||||
|
moving: position.moving,
|
||||||
|
};
|
||||||
|
client.viewport = viewport;
|
||||||
|
|
||||||
|
// update position in the world
|
||||||
|
const world = this.Worlds.get(client.roomId);
|
||||||
|
if (!world) {
|
||||||
|
console.error("In USER_POSITION, could not find world with id '", client.roomId, "'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
world.updatePosition(client, client.position);
|
||||||
|
world.setViewport(client, client.viewport);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('An error occurred on "user_position" event');
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Useless now, will be useful again if we allow editing details in game
|
||||||
|
handleSetPlayerDetails(client: ExSocketInterface, playerDetailsMessage: SetPlayerDetailsMessage) {
|
||||||
|
const playerDetails = {
|
||||||
|
name: playerDetailsMessage.getName(),
|
||||||
|
characterLayers: playerDetailsMessage.getCharacterlayersList()
|
||||||
|
};
|
||||||
|
//console.log(SocketIoEvent.SET_PLAYER_DETAILS, playerDetails);
|
||||||
|
if (!isSetPlayerDetailsMessage(playerDetails)) {
|
||||||
|
emitError(client, 'Invalid SET_PLAYER_DETAILS message received: ');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
client.name = playerDetails.name;
|
||||||
|
client.characterLayers = playerDetails.characterLayers;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSilentMessage(client: ExSocketInterface, silentMessage: SilentMessage) {
|
||||||
|
try {
|
||||||
|
// update position in the world
|
||||||
|
const world = this.Worlds.get(client.roomId);
|
||||||
|
if (!world) {
|
||||||
|
console.error("In handleSilentMessage, could not find world with id '", client.roomId, "'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
world.setSilent(client, silentMessage.getSilent());
|
||||||
|
} catch (e) {
|
||||||
|
console.error('An error occurred on "handleSilentMessage"');
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleItemEvent(ws: ExSocketInterface, itemEventMessage: ItemEventMessage) {
|
||||||
|
const itemEvent = ProtobufUtils.toItemEvent(itemEventMessage);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const world = this.Worlds.get(ws.roomId);
|
||||||
|
if (!world) {
|
||||||
|
console.error("Could not find world with id '", ws.roomId, "'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subMessage = new SubMessage();
|
||||||
|
subMessage.setItemeventmessage(itemEventMessage);
|
||||||
|
|
||||||
|
// Let's send the event without using the SocketIO room.
|
||||||
|
for (const user of world.getUsers().values()) {
|
||||||
|
const client = this.searchClientByIdOrFail(user.id);
|
||||||
|
//client.emit(SocketIoEvent.ITEM_EVENT, itemEvent);
|
||||||
|
emitInBatch(client, subMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
world.setItemState(itemEvent.itemId, itemEvent.state);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('An error occurred on "item_event"');
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async 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
|
||||||
|
await adminApi.reportPlayer(reportedSocket.userUuid, reportPlayerMessage.getReportcomment(), client.userUuid)
|
||||||
|
} 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());
|
||||||
|
if (client === undefined) {
|
||||||
|
console.warn("While exchanging a WebRTC signal: client with id ", data.getReceiverid(), " does not exist. This might be a race condition.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const webrtcSignalToClient = new WebRtcSignalToClientMessage();
|
||||||
|
webrtcSignalToClient.setUserid(socket.userId);
|
||||||
|
webrtcSignalToClient.setSignal(data.getSignal());
|
||||||
|
|
||||||
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
|
serverToClientMessage.setWebrtcsignaltoclientmessage(webrtcSignalToClient);
|
||||||
|
|
||||||
|
if (!client.disconnecting) {
|
||||||
|
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emitScreenSharing(socket: ExSocketInterface, data: WebRtcSignalToServerMessage): void {
|
||||||
|
//send only at user
|
||||||
|
const client = this.sockets.get(data.getReceiverid());
|
||||||
|
if (client === undefined) {
|
||||||
|
console.warn("While exchanging a WEBRTC_SCREEN_SHARING signal: client with id ", data.getReceiverid(), " does not exist. This might be a race condition.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const webrtcSignalToClient = new WebRtcSignalToClientMessage();
|
||||||
|
webrtcSignalToClient.setUserid(socket.userId);
|
||||||
|
webrtcSignalToClient.setSignal(data.getSignal());
|
||||||
|
|
||||||
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
|
serverToClientMessage.setWebrtcscreensharingsignaltoclientmessage(webrtcSignalToClient);
|
||||||
|
|
||||||
|
if (!client.disconnecting) {
|
||||||
|
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private searchClientByIdOrFail(userId: number): ExSocketInterface {
|
||||||
|
const client: ExSocketInterface|undefined = this.sockets.get(userId);
|
||||||
|
if (client === undefined) {
|
||||||
|
throw new Error("Could not find user with id " + userId);
|
||||||
|
}
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
leaveRoom(Client : ExSocketInterface){
|
||||||
|
// leave previous room and world
|
||||||
|
if(Client.roomId){
|
||||||
|
try {
|
||||||
|
//user leave previous world
|
||||||
|
const world: GameRoom | undefined = this.Worlds.get(Client.roomId);
|
||||||
|
if (world) {
|
||||||
|
world.leave(Client);
|
||||||
|
if (world.isEmpty()) {
|
||||||
|
this.Worlds.delete(Client.roomId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//user leave previous room
|
||||||
|
//Client.leave(Client.roomId);
|
||||||
|
} finally {
|
||||||
|
this.nbClientsPerRoomGauge.dec({ room: Client.roomId });
|
||||||
|
//delete Client.roomId;
|
||||||
|
this.sockets.delete(Client.userId);
|
||||||
|
// Let's log server load when a user leaves
|
||||||
|
this.nbClientsGauge.dec();
|
||||||
|
console.log('A user left (', this.sockets.size, ' connected users)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOrCreateRoom(roomId: string): Promise<GameRoom> {
|
||||||
|
//check and create new world for a room
|
||||||
|
let world = this.Worlds.get(roomId)
|
||||||
|
if(world === undefined){
|
||||||
|
world = new GameRoom(
|
||||||
|
roomId,
|
||||||
|
(user: User, group: Group) => this.joinWebRtcRoom(user, group),
|
||||||
|
(user: User, group: Group) => this.disConnectedUser(user, group),
|
||||||
|
MINIMUM_DISTANCE,
|
||||||
|
GROUP_RADIUS,
|
||||||
|
(thing: Movable, listener: User) => this.onRoomEnter(thing, listener),
|
||||||
|
(thing: Movable, position:PositionInterface, listener:User) => this.onClientMove(thing, position, listener),
|
||||||
|
(thing: Movable, listener:User) => this.onClientLeave(thing, listener)
|
||||||
|
);
|
||||||
|
if (!world.anonymous) {
|
||||||
|
const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug)
|
||||||
|
world.tags = data.tags
|
||||||
|
world.policyType = Number(data.policy_type)
|
||||||
|
}
|
||||||
|
this.Worlds.set(roomId, world);
|
||||||
|
}
|
||||||
|
return Promise.resolve(world)
|
||||||
|
}
|
||||||
|
|
||||||
|
private joinRoom(client : ExSocketInterface, position: PointInterface): GameRoom {
|
||||||
|
|
||||||
|
const roomId = client.roomId;
|
||||||
|
//join user in room
|
||||||
|
this.nbClientsPerRoomGauge.inc({ room: roomId });
|
||||||
|
client.position = position;
|
||||||
|
|
||||||
|
const world = this.Worlds.get(roomId)
|
||||||
|
if(world === undefined){
|
||||||
|
throw new Error('Could not find room for ID: '+client.roomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch groups position to newly connected user
|
||||||
|
world.getGroups().forEach((group: Group) => {
|
||||||
|
this.emitCreateUpdateGroupEvent(client, group);
|
||||||
|
});
|
||||||
|
//join world
|
||||||
|
world.join(client, client.position);
|
||||||
|
return world;
|
||||||
|
}
|
||||||
|
|
||||||
|
private onRoomEnter(thing: Movable, listener: User) {
|
||||||
|
const clientListener = this.searchClientByIdOrFail(listener.id);
|
||||||
|
if (thing instanceof User) {
|
||||||
|
const clientUser = this.searchClientByIdOrFail(thing.id);
|
||||||
|
|
||||||
|
const userJoinedMessage = new UserJoinedMessage();
|
||||||
|
if (!Number.isInteger(clientUser.userId)) {
|
||||||
|
throw new Error('clientUser.userId is not an integer '+clientUser.userId);
|
||||||
|
}
|
||||||
|
userJoinedMessage.setUserid(clientUser.userId);
|
||||||
|
userJoinedMessage.setName(clientUser.name);
|
||||||
|
userJoinedMessage.setCharacterlayersList(clientUser.characterLayers);
|
||||||
|
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(clientUser.position));
|
||||||
|
|
||||||
|
const subMessage = new SubMessage();
|
||||||
|
subMessage.setUserjoinedmessage(userJoinedMessage);
|
||||||
|
|
||||||
|
emitInBatch(clientListener, subMessage);
|
||||||
|
} else if (thing instanceof Group) {
|
||||||
|
this.emitCreateUpdateGroupEvent(clientListener, thing);
|
||||||
|
} else {
|
||||||
|
console.error('Unexpected type for Movable.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onClientMove(thing: Movable, position:PositionInterface, listener:User): void {
|
||||||
|
const clientListener = this.searchClientByIdOrFail(listener.id);
|
||||||
|
if (thing instanceof User) {
|
||||||
|
const clientUser = this.searchClientByIdOrFail(thing.id);
|
||||||
|
|
||||||
|
const userMovedMessage = new UserMovedMessage();
|
||||||
|
userMovedMessage.setUserid(clientUser.userId);
|
||||||
|
userMovedMessage.setPosition(ProtobufUtils.toPositionMessage(clientUser.position));
|
||||||
|
|
||||||
|
const subMessage = new SubMessage();
|
||||||
|
subMessage.setUsermovedmessage(userMovedMessage);
|
||||||
|
|
||||||
|
clientListener.emitInBatch(subMessage);
|
||||||
|
//console.log("Sending USER_MOVED event");
|
||||||
|
} else if (thing instanceof Group) {
|
||||||
|
this.emitCreateUpdateGroupEvent(clientListener, thing);
|
||||||
|
} else {
|
||||||
|
console.error('Unexpected type for Movable.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onClientLeave(thing: Movable, listener:User) {
|
||||||
|
const clientListener = this.searchClientByIdOrFail(listener.id);
|
||||||
|
if (thing instanceof User) {
|
||||||
|
const clientUser = this.searchClientByIdOrFail(thing.id);
|
||||||
|
this.emitUserLeftEvent(clientListener, clientUser.userId);
|
||||||
|
} else if (thing instanceof Group) {
|
||||||
|
this.emitDeleteGroupEvent(clientListener, thing.getId());
|
||||||
|
} else {
|
||||||
|
console.error('Unexpected type for Movable.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitCreateUpdateGroupEvent(client: ExSocketInterface, group: Group): void {
|
||||||
|
const position = group.getPosition();
|
||||||
|
const pointMessage = new PointMessage();
|
||||||
|
pointMessage.setX(Math.floor(position.x));
|
||||||
|
pointMessage.setY(Math.floor(position.y));
|
||||||
|
const groupUpdateMessage = new GroupUpdateMessage();
|
||||||
|
groupUpdateMessage.setGroupid(group.getId());
|
||||||
|
groupUpdateMessage.setPosition(pointMessage);
|
||||||
|
|
||||||
|
const subMessage = new SubMessage();
|
||||||
|
subMessage.setGroupupdatemessage(groupUpdateMessage);
|
||||||
|
|
||||||
|
emitInBatch(client, subMessage);
|
||||||
|
//socket.emit(SocketIoEvent.GROUP_CREATE_UPDATE, groupUpdateMessage.serializeBinary().buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitDeleteGroupEvent(client: ExSocketInterface, groupId: number): void {
|
||||||
|
const groupDeleteMessage = new GroupDeleteMessage();
|
||||||
|
groupDeleteMessage.setGroupid(groupId);
|
||||||
|
|
||||||
|
const subMessage = new SubMessage();
|
||||||
|
subMessage.setGroupdeletemessage(groupDeleteMessage);
|
||||||
|
|
||||||
|
emitInBatch(client, subMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitUserLeftEvent(client: ExSocketInterface, userId: number): void {
|
||||||
|
const userLeftMessage = new UserLeftMessage();
|
||||||
|
userLeftMessage.setUserid(userId);
|
||||||
|
|
||||||
|
const subMessage = new SubMessage();
|
||||||
|
subMessage.setUserleftmessage(userLeftMessage);
|
||||||
|
|
||||||
|
emitInBatch(client, subMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private joinWebRtcRoom(user: User, group: Group) {
|
||||||
|
/*const roomId: string = "webrtcroom"+group.getId();
|
||||||
|
if (user.socket.webRtcRoomId === roomId) {
|
||||||
|
return;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
for (const otherUser of group.getUsers()) {
|
||||||
|
if (user === otherUser) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's send 2 messages: one to the user joining the group and one to the other user
|
||||||
|
const webrtcStartMessage1 = new WebRtcStartMessage();
|
||||||
|
webrtcStartMessage1.setUserid(otherUser.id);
|
||||||
|
webrtcStartMessage1.setName(otherUser.socket.name);
|
||||||
|
webrtcStartMessage1.setInitiator(true);
|
||||||
|
|
||||||
|
const serverToClientMessage1 = new ServerToClientMessage();
|
||||||
|
serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1);
|
||||||
|
|
||||||
|
if (!user.socket.disconnecting) {
|
||||||
|
user.socket.send(serverToClientMessage1.serializeBinary().buffer, true);
|
||||||
|
//console.log('Sending webrtcstart initiator to '+user.socket.userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const webrtcStartMessage2 = new WebRtcStartMessage();
|
||||||
|
webrtcStartMessage2.setUserid(user.id);
|
||||||
|
webrtcStartMessage2.setName(user.socket.name);
|
||||||
|
webrtcStartMessage2.setInitiator(false);
|
||||||
|
|
||||||
|
const serverToClientMessage2 = new ServerToClientMessage();
|
||||||
|
serverToClientMessage2.setWebrtcstartmessage(webrtcStartMessage2);
|
||||||
|
|
||||||
|
if (!otherUser.socket.disconnecting) {
|
||||||
|
otherUser.socket.send(serverToClientMessage2.serializeBinary().buffer, true);
|
||||||
|
//console.log('Sending webrtcstart to '+otherUser.socket.userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//disconnect user
|
||||||
|
private disConnectedUser(user: User, group: Group) {
|
||||||
|
// Most of the time, sending a disconnect event to one of the players is enough (the player will close the connection
|
||||||
|
// which will be shut for the other 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).
|
||||||
|
// So we also send the disconnect event to the other player.
|
||||||
|
for (const otherUser of group.getUsers()) {
|
||||||
|
if (user === otherUser) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const webrtcDisconnectMessage1 = new WebRtcDisconnectMessage();
|
||||||
|
webrtcDisconnectMessage1.setUserid(user.id);
|
||||||
|
|
||||||
|
const serverToClientMessage1 = new ServerToClientMessage();
|
||||||
|
serverToClientMessage1.setWebrtcdisconnectmessage(webrtcDisconnectMessage1);
|
||||||
|
|
||||||
|
if (!otherUser.socket.disconnecting) {
|
||||||
|
otherUser.socket.send(serverToClientMessage1.serializeBinary().buffer, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const webrtcDisconnectMessage2 = new WebRtcDisconnectMessage();
|
||||||
|
webrtcDisconnectMessage2.setUserid(otherUser.id);
|
||||||
|
|
||||||
|
const serverToClientMessage2 = new ServerToClientMessage();
|
||||||
|
serverToClientMessage2.setWebrtcdisconnectmessage(webrtcDisconnectMessage2);
|
||||||
|
|
||||||
|
if (!user.socket.disconnecting) {
|
||||||
|
user.socket.send(serverToClientMessage2.serializeBinary().buffer, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emitPlayGlobalMessage(client: ExSocketInterface, playglobalmessage: PlayGlobalMessage) {
|
||||||
|
try {
|
||||||
|
const world = this.Worlds.get(client.roomId);
|
||||||
|
if (!world) {
|
||||||
|
console.error("In emitPlayGlobalMessage, could not find world with id '", client.roomId, "'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
|
serverToClientMessage.setPlayglobalmessage(playglobalmessage);
|
||||||
|
|
||||||
|
for (const [id, user] of world.getUsers().entries()) {
|
||||||
|
user.socket.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('An error occurred on "emitPlayGlobalMessage" event');
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public getWorlds(): Map<string, GameRoom> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const socketManager = new SocketManager();
|
Loading…
Reference in New Issue
Block a user