workadventure/back/src/Controller/IoSocketController.ts

323 lines
15 KiB
TypeScript
Raw Normal View History

2020-10-20 16:39:23 +02:00
import {CharacterLayer, ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.."
import {GameRoomPolicyTypes} from "../Model/GameRoom";
2020-09-21 11:24:03 +02:00
import {PointInterface} from "../Model/Websocket/PointInterface";
import {
SetPlayerDetailsMessage,
SubMessage,
2020-09-24 17:24:37 +02:00
BatchMessage,
2020-09-28 18:52:54 +02:00
ItemEventMessage,
ViewportMessage,
ClientToServerMessage,
2020-09-29 16:01:22 +02:00
SilentMessage,
WebRtcSignalToServerMessage,
2020-10-06 15:37:00 +02:00
PlayGlobalMessage,
ReportPlayerMessage,
QueryJitsiJwtMessage
} from "../Messages/generated/messages_pb";
import {UserMovesMessage} from "../Messages/generated/messages_pb";
import {TemplatedApp} from "uWebSockets.js"
import {parse} from "query-string";
2020-10-09 14:53:18 +02:00
import {jwtTokenManager} from "../Services/JWTTokenManager";
2020-10-20 16:39:23 +02:00
import {adminApi, CharacterTexture, FetchMemberDataByUuidResponse} from "../Services/AdminApi";
import {SocketManager, socketManager} from "../Services/SocketManager";
import {emitInBatch, resetPing} from "../Services/IoSocketHelpers";
import {clientEventsEmitter} from "../Services/ClientEventsEmitter";
import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable";
2020-05-03 16:28:18 +02:00
export class IoSocketController {
private nextUserId: number = 1;
2020-05-03 16:28:18 +02:00
2020-09-28 18:52:54 +02:00
constructor(private readonly app: TemplatedApp) {
this.ioConnection();
this.adminRoomSocket();
}
adminRoomSocket() {
this.app.ws('/admin/rooms', {
upgrade: (res, req, context) => {
const query = parse(req.getQuery());
const websocketKey = req.getHeader('sec-websocket-key');
const websocketProtocol = req.getHeader('sec-websocket-protocol');
const websocketExtensions = req.getHeader('sec-websocket-extensions');
const token = query.token;
if (token !== ADMIN_API_TOKEN) {
console.log('Admin access refused for token: '+token)
res.writeStatus("401 Unauthorized").end('Incorrect token');
}
const roomId = query.roomId as string;
res.upgrade(
{roomId},
websocketKey, websocketProtocol, websocketExtensions, context,
);
},
open: (ws) => {
console.log('Admin socket connect for room: '+ws.roomId);
ws.send('Data:'+JSON.stringify(socketManager.getAdminSocketDataFor(ws.roomId as string)));
ws.clientJoinCallback = (clientUUid: string, roomId: string) => {
const wsroomId = ws.roomId as string;
if(wsroomId === roomId) {
ws.send('MemberJoin:'+clientUUid+';'+roomId);
}
};
ws.clientLeaveCallback = (clientUUid: string, roomId: string) => {
const wsroomId = ws.roomId as string;
if(wsroomId === roomId) {
ws.send('MemberLeave:'+clientUUid+';'+roomId);
}
};
clientEventsEmitter.registerToClientJoin(ws.clientJoinCallback);
clientEventsEmitter.registerToClientLeave(ws.clientLeaveCallback);
},
message: (ws, arrayBuffer, isBinary): void => {
try {
//TODO refactor message type and data
2020-10-19 21:04:16 +02:00
const message: {event: string, message: {type: string, message: unknown, userUuid: string}} =
JSON.parse(new TextDecoder("utf-8").decode(new Uint8Array(arrayBuffer)));
if(message.event === 'user-message') {
2020-10-20 08:30:11 +02:00
const messageToEmit = (message.message as { message: string, type: string, userUuid: string });
switch (message.message.type) {
2020-10-20 09:24:06 +02:00
case 'ban': {
2020-10-20 08:30:11 +02:00
socketManager.emitSendUserMessage(messageToEmit);
break;
2020-10-20 09:24:06 +02:00
}
case 'banned': {
2020-10-20 08:30:11 +02:00
const socketUser = socketManager.emitSendUserMessage(messageToEmit);
setTimeout(() => {
socketUser.close();
}, 10000);
break;
2020-10-20 09:24:06 +02:00
}
default: {
break;
}
2020-10-20 08:20:21 +02:00
}
}
}catch (err) {
console.error(err);
}
},
close: (ws, code, message) => {
//todo make sure this code unregister the right listeners
clientEventsEmitter.unregisterFromClientJoin(ws.clientJoinCallback);
clientEventsEmitter.unregisterFromClientLeave(ws.clientLeaveCallback);
}
})
}
2020-09-28 18:52:54 +02:00
ioConnection() {
this.app.ws('/room', {
2020-09-28 18:52:54 +02:00
/* Options */
//compression: uWS.SHARED_COMPRESSOR,
maxPayloadLength: 16 * 1024 * 1024,
2020-09-30 12:16:39 +02:00
maxBackpressure: 65536, // Maximum 64kB of data in the buffer.
2020-09-29 16:01:22 +02:00
//idleTimeout: 10,
upgrade: (res, req, context) => {
2020-09-30 12:16:39 +02:00
//console.log('An Http connection wants to become WebSocket, URL: ' + req.getUrl() + '!');
(async () => {
/* Keep track of abortions */
const upgradeAborted = {aborted: false};
res.onAborted(() => {
/* We can simply signal that we were aborted */
upgradeAborted.aborted = true;
2020-07-27 22:36:07 +02:00
});
try {
2020-10-09 16:18:25 +02:00
const url = req.getUrl();
2020-10-06 15:37:00 +02:00
const query = parse(req.getQuery());
2020-10-09 16:18:25 +02:00
const websocketKey = req.getHeader('sec-websocket-key');
const websocketProtocol = req.getHeader('sec-websocket-protocol');
const websocketExtensions = req.getHeader('sec-websocket-extensions');
2020-10-06 15:37:00 +02:00
const roomId = query.roomId;
if (typeof roomId !== 'string') {
throw new Error('Undefined room ID: ');
}
2020-10-06 15:37:00 +02:00
const token = query.token;
const x = Number(query.x);
const y = Number(query.y);
const top = Number(query.top);
const bottom = Number(query.bottom);
const left = Number(query.left);
const right = Number(query.right);
const name = query.name;
if (typeof name !== 'string') {
throw new Error('Expecting name');
}
if (name === '') {
throw new Error('No empty name');
}
let characterLayers = query.characterLayers;
if (characterLayers === null) {
throw new Error('Expecting skin');
}
if (typeof characterLayers === 'string') {
characterLayers = [ characterLayers ];
}
2020-10-06 15:37:00 +02:00
2020-10-09 14:53:18 +02:00
const userUuid = await jwtTokenManager.getUserUuidFromToken(token);
let memberTags: string[] = [];
2020-10-20 16:39:23 +02:00
let memberTextures: CharacterTexture[] = [];
const room = await socketManager.getOrCreateRoom(roomId);
2020-10-20 16:39:23 +02:00
try {
const userData = await adminApi.fetchMemberDataByUuid(userUuid);
//console.log('USERDATA', userData)
memberTags = userData.tags;
memberTextures = userData.textures;
if (!room.anonymous && room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && !room.canAccess(memberTags)) {
throw new Error('No correct tags')
}
2020-10-20 16:39:23 +02:00
//console.log('access granted for user '+userUuid+' and room '+roomId);
} catch (e) {
console.log('access not granted for user '+userUuid+' and room '+roomId);
throw new Error('Client cannot acces this ressource.')
2020-10-09 14:53:18 +02:00
}
2020-10-20 16:39:23 +02:00
// Generate characterLayers objects from characterLayers string[]
const characterLayerObjs: CharacterLayer[] = SocketManager.mergeCharacterLayersAndCustomTextures(characterLayers, memberTextures);
if (upgradeAborted.aborted) {
console.log("Ouch! Client disconnected before we could upgrade it!");
/* You must not upgrade now */
return;
}
/* This immediately calls open handler, you must not use res after this call */
res.upgrade({
// Data passed here is accessible on the "websocket" socket object.
2020-10-09 16:18:25 +02:00
url,
2020-10-06 15:37:00 +02:00
token,
userUuid,
roomId,
name,
2020-10-20 16:39:23 +02:00
characterLayers: characterLayerObjs,
tags: memberTags,
2020-10-20 16:39:23 +02:00
textures: memberTextures,
position: {
x: x,
y: y,
direction: 'down',
moving: false
} as PointInterface,
viewport: {
top,
right,
bottom,
left
}
},
/* Spell these correctly */
2020-10-09 16:18:25 +02:00
websocketKey,
websocketProtocol,
websocketExtensions,
context);
2020-09-30 10:17:01 +02:00
} catch (e) {
if (e instanceof Error) {
2020-10-09 16:18:25 +02:00
console.log(e.message);
res.writeStatus("401 Unauthorized").end(e.message);
} else {
2020-10-09 16:18:25 +02:00
console.log(e);
res.writeStatus("500 Internal Server Error").end('An error occurred');
}
return;
}
})();
},
2020-09-28 18:52:54 +02:00
/* Handlers */
open: (ws) => {
// Let's join the room
const client = this.initClient(ws); //todo: into the upgrade instead?
socketManager.handleJoinRoom(client);
resetPing(client);
//get data information and shwo messages
2020-10-20 16:39:23 +02:00
adminApi.fetchMemberDataByUuid(client.userUuid).then((res: FetchMemberDataByUuidResponse) => {
if (!res.messages) {
return;
}
res.messages.forEach((c: unknown) => {
2020-10-20 09:24:06 +02:00
const messageToSend = c as { type: string, message: string };
socketManager.emitSendUserMessage({
userUuid: client.userUuid,
type: messageToSend.type,
message: messageToSend.message
})
});
}).catch((err) => {
console.error('fetchMemberDataByUuid => err', err);
});
2020-09-28 18:52:54 +02:00
},
2020-10-06 15:37:00 +02:00
message: (ws, arrayBuffer, isBinary): void => {
2020-09-28 18:52:54 +02:00
const client = ws as ExSocketInterface;
const message = ClientToServerMessage.deserializeBinary(new Uint8Array(arrayBuffer));
2020-10-06 15:37:00 +02:00
if (message.hasViewportmessage()) {
socketManager.handleViewport(client, message.getViewportmessage() as ViewportMessage);
2020-09-28 18:52:54 +02:00
} else if (message.hasUsermovesmessage()) {
socketManager.handleUserMovesMessage(client, message.getUsermovesmessage() as UserMovesMessage);
2020-09-28 18:52:54 +02:00
} else if (message.hasSetplayerdetailsmessage()) {
socketManager.handleSetPlayerDetails(client, message.getSetplayerdetailsmessage() as SetPlayerDetailsMessage);
2020-09-28 18:52:54 +02:00
} else if (message.hasSilentmessage()) {
socketManager.handleSilentMessage(client, message.getSilentmessage() as SilentMessage);
2020-09-28 18:52:54 +02:00
} else if (message.hasItemeventmessage()) {
socketManager.handleItemEvent(client, message.getItemeventmessage() as ItemEventMessage);
2020-09-29 16:01:22 +02:00
} else if (message.hasWebrtcsignaltoservermessage()) {
socketManager.emitVideo(client, message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage);
2020-09-29 16:01:22 +02:00
} else if (message.hasWebrtcscreensharingsignaltoservermessage()) {
socketManager.emitScreenSharing(client, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage);
} else if (message.hasPlayglobalmessage()) {
socketManager.emitPlayGlobalMessage(client, message.getPlayglobalmessage() as PlayGlobalMessage);
2020-10-12 11:22:41 +02:00
} else if (message.hasReportplayermessage()){
socketManager.handleReportMessage(client, message.getReportplayermessage() as ReportPlayerMessage);
2020-10-16 19:13:26 +02:00
} else if (message.hasQueryjitsijwtmessage()){
socketManager.handleQueryJitsiJwtMessage(client, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
}
2020-06-11 23:18:06 +02:00
2020-09-28 18:52:54 +02:00
/* 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 {
2020-09-28 18:52:54 +02:00
Client.disconnecting = true;
//leave room
socketManager.leaveRoom(Client);
} catch (e) {
console.error('An error occurred on "disconnect"');
console.error(e);
}
2020-09-28 18:52:54 +02:00
}
})
}
//eslint-disable-next-line @typescript-eslint/no-explicit-any
private initClient(ws: any): ExSocketInterface {
const client : ExSocketInterface = ws;
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;
2020-10-20 16:39:23 +02:00
client.textures = ws.textures;
client.characterLayers = ws.characterLayers;
client.roomId = ws.roomId;
return client;
2020-05-03 16:28:18 +02:00
}
2020-04-04 22:35:20 +02:00
}