Migrating to uWS
This commit is contained in:
parent
783d58d3cb
commit
6a4c0c8678
31 changed files with 2056 additions and 1123 deletions
|
@ -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,
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
10
back/src/Controller/BaseController.ts
Normal file
10
back/src/Controller/BaseController.ts
Normal 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', '*');
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue