Migrating to uWS

This commit is contained in:
David Négrier 2020-09-28 18:52:54 +02:00
parent 783d58d3cb
commit 6a4c0c8678
31 changed files with 2056 additions and 1123 deletions

View file

@ -3,38 +3,58 @@ import Jwt from "jsonwebtoken";
import {BAD_REQUEST, OK} from "http-status-codes";
import {SECRET_KEY, URL_ROOM_STARTED} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..."
import { uuid } from 'uuidv4';
import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js";
import {BaseController} from "./BaseController";
export interface TokenInterface {
name: string,
userUuid: string
}
export class AuthenticateController {
App : Application;
export class AuthenticateController extends BaseController {
constructor(App : Application) {
this.App = App;
constructor(private App : TemplatedApp) {
super();
this.login();
}
onAbortedOrFinishedResponse(res: HttpResponse/*, readStream: any*/) {
console.log("ERROR! onAbortedOrFinishedResponse called!");
/*if (res.id == -1) {
console.log("ERROR! onAbortedOrFinishedResponse called twice for the same res!");
} else {
console.log('Stream was closed, openStreams: ' + --openStreams);
console.timeEnd(res.id);
readStream.destroy();
}*/
/* Mark this response already accounted for */
//res.id = -1;
}
//permit to login on application. Return token to connect on Websocket IO.
login(){
// For now, let's completely forget the /login route.
this.App.post("/login", (req: Request, res: Response) => {
const param = req.body;
/*if(!param.name){
return res.status(BAD_REQUEST).send({
message: "email parameter is empty"
});
}*/
//TODO check user email for The Coding Machine game
this.App.options("/login", (res: HttpResponse, req: HttpRequest) => {
this.addCorsHeaders(res);
res.end();
});
this.App.post("/login", async (res: HttpResponse, req: HttpRequest) => {
this.addCorsHeaders(res);
res.onAborted(() => {
console.warn('Login request was aborted');
})
const param = await res.json();
const userUuid = uuid();
const token = Jwt.sign({name: param.name, userUuid: userUuid} as TokenInterface, SECRET_KEY, {expiresIn: '24h'});
return res.status(OK).send({
res.writeStatus("200 OK").end(JSON.stringify({
token: token,
mapUrlStart: URL_ROOM_STARTED,
userId: userUuid,
});
}));
});
}
}

View file

@ -0,0 +1,10 @@
import {HttpResponse} from "uWebSockets.js";
export class BaseController {
protected addCorsHeaders(res: HttpResponse): void {
res.writeHeader('access-control-allow-headers', 'Origin, X-Requested-With, Content-Type, Accept');
res.writeHeader('access-control-allow-methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.writeHeader('access-control-allow-origin', '*');
}
}

View file

@ -1,5 +1,3 @@
import socketIO = require('socket.io');
import {Socket} from "socket.io";
import * as http from "http";
import {MessageUserPosition, Point} from "../Model/Websocket/MessageUserPosition"; //TODO fix import by "_Model/.."
import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.."
@ -32,11 +30,19 @@ import {
GroupDeleteMessage,
UserJoinedMessage,
UserLeftMessage,
ItemEventMessage, ViewportMessage
ItemEventMessage,
ViewportMessage,
ClientToServerMessage,
JoinRoomMessage,
ErrorMessage,
RoomJoinedMessage,
ItemStateMessage,
ServerToClientMessage, SetUserIdMessage, SilentMessage
} from "../Messages/generated/messages_pb";
import {UserMovesMessage} from "../Messages/generated/messages_pb";
import Direction = PositionMessage.Direction;
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
import {App, TemplatedApp, WebSocket} from "uWebSockets.js"
enum SocketIoEvent {
CONNECTION = "connection",
@ -59,12 +65,18 @@ enum SocketIoEvent {
BATCH = "batch",
}
function emitInBatch(socket: ExSocketInterface, event: string, payload: SubMessage): void {
function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void {
if (socket.disconnecting) {
return;
}
socket.batchedMessages.addPayload(payload);
if (socket.batchTimeout === null) {
socket.batchTimeout = setTimeout(() => {
socket./*binary(true).*/emit(SocketIoEvent.BATCH, socket.batchedMessages.serializeBinary().buffer);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setBatchmessage(socket.batchedMessages);
socket.send(serverToClientMessage.serializeBinary().buffer, true);
socket.batchedMessages = new BatchMessage();
socket.batchTimeout = null;
}, 100);
@ -72,15 +84,14 @@ function emitInBatch(socket: ExSocketInterface, event: string, payload: SubMessa
}
export class IoSocketController {
public readonly Io: socketIO.Server;
private Worlds: Map<string, World> = new Map<string, World>();
private sockets: Map<number, ExSocketInterface> = new Map<number, ExSocketInterface>();
private nbClientsGauge: Gauge<string>;
private nbClientsPerRoomGauge: Gauge<string>;
private nextUserId: number = 1;
constructor(server: http.Server) {
this.Io = socketIO(server);
constructor(private readonly app: TemplatedApp) {
this.nbClientsGauge = new Gauge({
name: 'workadventure_nb_sockets',
help: 'Number of connected sockets',
@ -92,52 +103,6 @@ export class IoSocketController {
labelNames: [ 'room' ]
});
// Authentication with token. it will be decoded and stored in the socket.
// Completely commented for now, as we do not use the "/login" route at all.
this.Io.use((socket: Socket, next) => {
//console.log(socket.handshake.query.token);
if (!socket.handshake.query || !socket.handshake.query.token) {
console.error('An authentication error happened, a user tried to connect without a token.');
return next(new Error('Authentication error'));
}
if(socket.handshake.query.token === 'test'){
if (ALLOW_ARTILLERY) {
(socket as ExSocketInterface).token = socket.handshake.query.token;
(socket as ExSocketInterface).userId = this.nextUserId;
(socket as ExSocketInterface).userUuid = uuid();
this.nextUserId++;
(socket as ExSocketInterface).isArtillery = true;
console.log((socket as ExSocketInterface).userId);
next();
return;
} else {
console.warn("In order to perform a load-testing test on this environment, you must set the ALLOW_ARTILLERY environment variable to 'true'");
next();
}
}
(socket as ExSocketInterface).isArtillery = false;
if(this.searchClientByToken(socket.handshake.query.token)){
console.error('An authentication error happened, a user tried to connect while its token is already connected.');
return next(new Error('Authentication error'));
}
Jwt.verify(socket.handshake.query.token, SECRET_KEY, (err: JsonWebTokenError, tokenDecoded: object) => {
if (err) {
console.error('An authentication error happened, invalid JsonWebToken.', err);
return next(new Error('Authentication error'));
}
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 = this.nextUserId;
(socket as ExSocketInterface).userUuid = tokenDecoded.userUuid;
this.nextUserId++;
next();
});
});
this.ioConnection();
}
@ -167,201 +132,107 @@ export class IoSocketController {
return null;
}
ioConnection() {
this.Io.on(SocketIoEvent.CONNECTION, (socket: Socket) => {
const client : ExSocketInterface = socket as ExSocketInterface;
client.batchedMessages = new BatchMessage();
client.batchTimeout = null;
client.emitInBatch = (event: string, payload: SubMessage): void => {
emitInBatch(client, event, payload);
private authenticate(ws: WebSocket) {
//console.log(socket.handshake.query.token);
/*if (!socket.handshake.query || !socket.handshake.query.token) {
console.error('An authentication error happened, a user tried to connect without a token.');
return next(new Error('Authentication error'));
}
if(socket.handshake.query.token === 'test'){
if (ALLOW_ARTILLERY) {
(socket as ExSocketInterface).token = socket.handshake.query.token;
(socket as ExSocketInterface).userId = this.nextUserId;
(socket as ExSocketInterface).userUuid = uuid();
this.nextUserId++;
(socket as ExSocketInterface).isArtillery = true;
console.log((socket as ExSocketInterface).userId);
next();
return;
} else {
console.warn("In order to perform a load-testing test on this environment, you must set the ALLOW_ARTILLERY environment variable to 'true'");
next();
}
}
(socket as ExSocketInterface).isArtillery = false;
if(this.searchClientByToken(socket.handshake.query.token)){
console.error('An authentication error happened, a user tried to connect while its token is already connected.');
return next(new Error('Authentication error'));
}
Jwt.verify(socket.handshake.query.token, SECRET_KEY, (err: JsonWebTokenError, tokenDecoded: object) => {
if (err) {
console.error('An authentication error happened, invalid JsonWebToken.', err);
return next(new Error('Authentication error'));
}
this.sockets.set(client.userId, client);
// Let's log server load when a user joins
const srvSockets = this.Io.sockets.sockets;
this.nbClientsGauge.inc();
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(' CPU: ', data.currentload, '%'));
// End log server load
if (!this.isValidToken(tokenDecoded)) {
return next(new Error('Authentication error, invalid token structure'));
}
/*join-rom event permit to join one room.
message :
userId : user identification
roomId: room identification
position: position of user in map
x: user x position on map
y: user y position on map
*/
socket.on(SocketIoEvent.JOIN_ROOM, (message: unknown, answerFn): void => {
//console.log(SocketIoEvent.JOIN_ROOM, message);
try {
if (!isJoinRoomMessageInterface(message)) {
socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid JOIN_ROOM message.'});
console.warn('Invalid JOIN_ROOM message received: ', message);
return;
}
const roomId = message.roomId;
(socket as ExSocketInterface).token = socket.handshake.query.token;
(socket as ExSocketInterface).userId = this.nextUserId;
(socket as ExSocketInterface).userUuid = tokenDecoded.userUuid;
this.nextUserId++;
next();
});*/
const socket = ws as ExSocketInterface;
socket.userId = this.nextUserId;
this.nextUserId++;
}
const Client = (socket as ExSocketInterface);
ioConnection() {
this.app.ws('/*', {
/* Options */
//compression: uWS.SHARED_COMPRESSOR,
maxPayloadLength: 16 * 1024 * 1024,
idleTimeout: 10,
/* Handlers */
open: (ws) => {
this.authenticate(ws);
// TODO: close if authenticate is ko
if (Client.roomId === roomId) {
return;
}
//leave previous room
this.leaveRoom(Client);
//join new previous room
const world = this.joinRoom(Client, roomId, message.position);
const things = world.setViewport(Client, message.viewport);
const listOfUsers: Array<MessageUserPosition> = [];
const listOfGroups: Array<GroupUpdateInterface> = [];
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;
}
listOfUsers.push(new MessageUserPosition(thing.id, player.name, player.characterLayers, player.position));
} else if (thing instanceof Group) {
listOfGroups.push({
groupId: thing.getId(),
position: thing.getPosition(),
});
} else {
console.error("Unexpected type for Movable returned by setViewport");
}
}
const listOfItems: {[itemId: string]: unknown} = {};
for (const [itemId, item] of world.getItemsState().entries()) {
listOfItems[itemId] = item;
}
//console.warn('ANSWER PLAYER POSITIONS', listOfUsers);
if (answerFn === undefined && ALLOW_ARTILLERY === true) {
// For some reason, answerFn can be undefined if we use Artillery (?)
return;
}
answerFn({
users: listOfUsers,
groups: listOfGroups,
items: listOfItems
});
} catch (e) {
console.error('An error occurred on "join_room" event');
console.error(e);
const client : ExSocketInterface = ws as ExSocketInterface;
client.batchedMessages = new BatchMessage();
client.batchTimeout = null;
client.emitInBatch = (payload: SubMessage): void => {
emitInBatch(client, payload);
}
});
client.disconnecting = false;
this.sockets.set(client.userId, client);
socket.on(SocketIoEvent.SET_VIEWPORT, (message: unknown): void => {
try {
if (!(message instanceof Buffer)) {
socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_VIEWPORT message. Expecting binary buffer.'});
console.warn('Invalid SET_VIEWPORT message received (expecting binary buffer): ', message);
return;
}
// 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)');
const viewportMessage = ViewportMessage.deserializeBinary(new Uint8Array(message as ArrayBuffer));
const viewport = viewportMessage.toObject();
},
message: (ws, arrayBuffer, isBinary) => {
const client = ws as ExSocketInterface;
const message = ClientToServerMessage.deserializeBinary(new Uint8Array(arrayBuffer));
const Client = (socket as ExSocketInterface);
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);
if (message.hasJoinroommessage()) {
this.handleJoinRoom(client, message.getJoinroommessage() as JoinRoomMessage);
} else if (message.hasViewportmessage()) {
this.handleViewport(client, message.getViewportmessage() as ViewportMessage);
} else if (message.hasUsermovesmessage()) {
this.handleUserMovesMessage(client, message.getUsermovesmessage() as UserMovesMessage);
} else if (message.hasSetplayerdetailsmessage()) {
this.handleSetPlayerDetails(client, message.getSetplayerdetailsmessage() as SetPlayerDetailsMessage);
} else if (message.hasSilentmessage()) {
this.handleSilentMessage(client, message.getSilentmessage() as SilentMessage);
} else if (message.hasItemeventmessage()) {
this.handleItemEvent(client, message.getItemeventmessage() as ItemEventMessage);
}
});
socket.on(SocketIoEvent.USER_POSITION, (message: unknown): void => {
//console.log(SockerIoEvent.USER_POSITION, userMovesMessage);
try {
if (!(message instanceof Buffer)) {
socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid USER_POSITION message. Expecting binary buffer.'});
console.warn('Invalid USER_POSITION message received (expecting binary buffer): ', message);
return;
}
const userMovesMessage = UserMovesMessage.deserializeBinary(new Uint8Array(message as ArrayBuffer));
const userMoves = userMovesMessage.toObject();
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");
}
const Client = (socket as ExSocketInterface);
// 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);
}
});
socket.on(SocketIoEvent.WEBRTC_SIGNAL, (data: unknown) => {
this.emitVideo((socket as ExSocketInterface), data);
});
socket.on(SocketIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL, (data: unknown) => {
this.emitScreenSharing((socket as ExSocketInterface), data);
});
socket.on(SocketIoEvent.DISCONNECT, () => {
const Client = (socket as ExSocketInterface);
/* Ok is false if backpressure was built up, wait for drain */
//let ok = ws.send(message, isBinary);
},
drain: (ws) => {
console.log('WebSocket backpressure: ' + ws.getBufferedAmount());
},
close: (ws, code, message) => {
const Client = (ws as ExSocketInterface);
try {
Client.disconnecting = true;
//leave room
this.leaveRoom(Client);
@ -377,112 +248,251 @@ export class IoSocketController {
console.error('An error occurred on "disconnect"');
console.error(e);
}
this.sockets.delete(Client.userId);
// Let's log server load when a user leaves
const srvSockets = this.Io.sockets.sockets;
this.nbClientsGauge.dec();
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('CPU: ', data.currentload, '%'));
// End log server load
console.log('A user left (', this.sockets.size, ' connected users)');
}
})
// TODO: finish this!
/*this.Io.on(SocketIoEvent.CONNECTION, (socket: Socket) => {
socket.on(SocketIoEvent.WEBRTC_SIGNAL, (data: unknown) => {
this.emitVideo((socket as ExSocketInterface), data);
});
// Let's send the user id to the user
socket.on(SocketIoEvent.SET_PLAYER_DETAILS, (message: unknown, answerFn) => {
//console.log(SocketIoEvent.SET_PLAYER_DETAILS, message);
if (!(message instanceof Buffer)) {
socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_PLAYER_DETAILS message. Expecting binary buffer.'});
console.warn('Invalid SET_PLAYER_DETAILS message received (expecting binary buffer): ', message);
return;
}
const playerDetailsMessage = SetPlayerDetailsMessage.deserializeBinary(new Uint8Array(message));
const playerDetails = {
name: playerDetailsMessage.getName(),
characterLayers: playerDetailsMessage.getCharacterlayersList()
};
//console.log(SocketIoEvent.SET_PLAYER_DETAILS, playerDetails);
if (!isSetPlayerDetailsMessage(playerDetails)) {
socket.emit(SocketIoEvent.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.characterLayers = playerDetails.characterLayers;
// Artillery fails when receiving an acknowledgement that is not a JSON object
if (!Client.isArtillery) {
answerFn(Client.userId);
}
socket.on(SocketIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL, (data: unknown) => {
this.emitScreenSharing((socket as ExSocketInterface), data);
});
socket.on(SocketIoEvent.SET_SILENT, (silent: unknown) => {
//console.log(SocketIoEvent.SET_SILENT, silent);
if (typeof silent !== "boolean") {
socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_SILENT message.'});
console.warn('Invalid SET_SILENT message received: ', silent);
return;
}
});*/
}
try {
const Client = (socket as ExSocketInterface);
private emitError(Client: ExSocketInterface, message: string): void {
const errorMessage = new ErrorMessage();
errorMessage.setMessage(message);
// update position in the world
const world = this.Worlds.get(Client.roomId);
if (!world) {
console.error("In SET_SILENT, could not find world with id '", Client.roomId, "'");
return;
}
world.setSilent(Client, silent);
} catch (e) {
console.error('An error occurred on "SET_SILENT"');
console.error(e);
}
});
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setErrormessage(errorMessage);
socket.on(SocketIoEvent.ITEM_EVENT, (message: unknown) => {
if (!(message instanceof Buffer)) {
socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid ITEM_EVENT message. Expecting binary buffer.'});
console.warn('Invalid ITEM_EVENT message received (expecting binary buffer): ', message);
return;
}
const itemEventMessage = ItemEventMessage.deserializeBinary(new Uint8Array(message));
if (!Client.disconnecting) {
Client.send(serverToClientMessage.serializeBinary().buffer);
}
console.warn(message);
}
const itemEvent = ProtobufUtils.toItemEvent(itemEventMessage);
private handleJoinRoom(Client: ExSocketInterface, message: JoinRoomMessage): void {
try {
/*if (!isJoinRoomMessageInterface(message.toObject())) {
console.log(message.toObject())
this.emitError(Client, 'Invalid JOIN_ROOM message received: ' + message.toObject().toString());
return;
}*/
const roomId = message.getRoomid();
/*if (!isItemEventMessageInterface(itemEvent)) {
socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid ITEM_EVENT message.'});
console.warn('Invalid ITEM_EVENT message received: ', itemEvent);
return;
}*/
try {
const Client = (socket as ExSocketInterface);
if (Client.roomId === roomId) {
return;
}
//socket.to(Client.roomId).emit(SocketIoEvent.ITEM_EVENT, itemEvent);
//leave previous room
this.leaveRoom(Client);
const world = this.Worlds.get(Client.roomId);
if (!world) {
console.error("Could not find world with id '", Client.roomId, "'");
return;
//join new previous room
const world = this.joinRoom(Client, roomId, ProtobufUtils.toPointInterface(message.getPosition() as PositionMessage));
const things = world.setViewport(Client, (message.getViewport() as ViewportMessage).toObject());
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 subMessage = new SubMessage();
subMessage.setItemeventmessage(itemEventMessage);
const userJoinedMessage = new UserJoinedMessage();
userJoinedMessage.setUserid(thing.id);
userJoinedMessage.setName(player.name);
userJoinedMessage.setCharacterlayersList(player.characterLayers);
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(player.position));
// 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, SocketIoEvent.ITEM_EVENT, subMessage);
}
roomJoinedMessage.addUser(userJoinedMessage);
} else if (thing instanceof Group) {
const groupUpdateMessage = new GroupUpdateMessage();
groupUpdateMessage.setGroupid(thing.getId());
groupUpdateMessage.setPosition(ProtobufUtils.toPointMessage(thing.getPosition()));
world.setItemState(itemEvent.itemId, itemEvent.state);
} catch (e) {
console.error('An error occurred on "item_event"');
console.error(e);
roomJoinedMessage.addGroup(groupUpdateMessage);
} else {
console.error("Unexpected type for Movable returned by setViewport");
}
});
});
}
for (const [itemId, item] of world.getItemsState().entries()) {
const itemStateMessage = new ItemStateMessage();
itemStateMessage.setItemid(itemId);
itemStateMessage.setStatejson(JSON.stringify(item));
roomJoinedMessage.addItem(itemStateMessage);
}
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();
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);
}
}
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;
const setUserIdMessage = new SetUserIdMessage();
setUserIdMessage.setUserid(client.userId);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSetuseridmessage(setUserIdMessage);
if (!client.disconnecting) {
client.send(serverToClientMessage.serializeBinary().buffer, true);
}
}
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);
}
}
emitVideo(socket: ExSocketInterface, data: unknown){
@ -542,7 +552,7 @@ export class IoSocketController {
}
}
//user leave previous room
Client.leave(Client.roomId);
//Client.leave(Client.roomId);
} finally {
this.nbClientsPerRoomGauge.dec({ room: Client.roomId });
delete Client.roomId;
@ -552,7 +562,7 @@ export class IoSocketController {
private joinRoom(Client : ExSocketInterface, roomId: string, position: PointInterface): World {
//join user in room
Client.join(roomId);
//Client.join(roomId);
this.nbClientsPerRoomGauge.inc({ room: roomId });
Client.roomId = roomId;
Client.position = position;
@ -570,6 +580,9 @@ export class IoSocketController {
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);
@ -578,7 +591,7 @@ export class IoSocketController {
const subMessage = new SubMessage();
subMessage.setUserjoinedmessage(userJoinedMessage);
emitInBatch(clientListener, SocketIoEvent.JOIN_ROOM, subMessage);
emitInBatch(clientListener, subMessage);
} else if (thing instanceof Group) {
this.emitCreateUpdateGroupEvent(clientListener, thing);
} else {
@ -596,7 +609,7 @@ export class IoSocketController {
const subMessage = new SubMessage();
subMessage.setUsermovedmessage(userMovedMessage);
clientListener.emitInBatch(SocketIoEvent.USER_MOVED, subMessage);
clientListener.emitInBatch(subMessage);
//console.log("Sending USER_MOVED event");
} else if (thing instanceof Group) {
this.emitCreateUpdateGroupEvent(clientListener, thing);
@ -627,7 +640,7 @@ export class IoSocketController {
return world;
}
private emitCreateUpdateGroupEvent(socket: Socket, group: Group): void {
private emitCreateUpdateGroupEvent(client: ExSocketInterface, group: Group): void {
const position = group.getPosition();
const pointMessage = new PointMessage();
pointMessage.setX(Math.floor(position.x));
@ -639,8 +652,7 @@ export class IoSocketController {
const subMessage = new SubMessage();
subMessage.setGroupupdatemessage(groupUpdateMessage);
const client : ExSocketInterface = socket as ExSocketInterface;
emitInBatch(client, SocketIoEvent.GROUP_CREATE_UPDATE, subMessage);
emitInBatch(client, subMessage);
//socket.emit(SocketIoEvent.GROUP_CREATE_UPDATE, groupUpdateMessage.serializeBinary().buffer);
}
@ -652,7 +664,7 @@ export class IoSocketController {
subMessage.setGroupdeletemessage(groupDeleteMessage);
const client : ExSocketInterface = socket as ExSocketInterface;
emitInBatch(client, SocketIoEvent.GROUP_DELETE, subMessage);
emitInBatch(client, subMessage);
}
private emitUserLeftEvent(socket: Socket, userId: number): void {
@ -663,7 +675,7 @@ export class IoSocketController {
subMessage.setUserleftmessage(userLeftMessage);
const client : ExSocketInterface = socket as ExSocketInterface;
emitInBatch(client, SocketIoEvent.USER_LEFT, subMessage);
emitInBatch(client, subMessage);
}
/**
@ -672,6 +684,10 @@ export class IoSocketController {
* @param roomId
*/
joinWebRtcRoom(socket: ExSocketInterface, roomId: string) {
// TODO: REBUILD THIS
return;
if (socket.webRtcRoomId === roomId) {
return;
}
@ -734,6 +750,9 @@ export class IoSocketController {
//disconnect user
disConnectedUser(userId: number, group: Group) {
// TODO: rebuild this
return;
const Client = this.searchClientByIdOrFail(userId);
Client.to("webrtcroom"+group.getId()).emit(SocketIoEvent.WEBRTC_DISCONNECT, {
userId: userId
@ -761,4 +780,5 @@ export class IoSocketController {
public getWorlds(): Map<string, World> {
return this.Worlds;
}
}

View file

@ -1,29 +1,33 @@
import express from "express";
import {Application, Request, Response} from "express";
import {OK} from "http-status-codes";
import {URL_ROOM_STARTED} from "../Enum/EnvironmentVariable";
import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js";
import {BaseController} from "./BaseController";
export class MapController {
App: Application;
export class MapController extends BaseController{
constructor(App: Application) {
constructor(private App : TemplatedApp) {
super();
this.App = App;
this.getStartMap();
this.assetMaps();
}
assetMaps() {
this.App.use('/map/files', express.static('src/Assets/Maps'));
}
// Returns a map mapping map name to file name of the map
getStartMap() {
this.App.get("/start-map", (req: Request, res: Response) => {
const url = req.headers.host?.replace('api.', 'maps.') + URL_ROOM_STARTED;
res.status(OK).send({
this.App.options("/start-map", (res: HttpResponse, req: HttpRequest) => {
this.addCorsHeaders(res);
res.end();
});
this.App.get("/start-map", (res: HttpResponse, req: HttpRequest) => {
this.addCorsHeaders(res);
const url = req.getHeader('host').replace('api.', 'maps.') + URL_ROOM_STARTED;
res.writeStatus("200 OK").end(JSON.stringify({
mapUrlStart: url,
startInstance: "global"
});
}));
});
}
}