Applying Prettier on pusher and back

This commit is contained in:
David Négrier 2021-06-24 10:09:10 +02:00
parent 06b7f5ba2f
commit 10c3d6dee2
71 changed files with 1848 additions and 1652 deletions

View file

@ -1,123 +1,147 @@
import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable";
import { ADMIN_API_TOKEN, ADMIN_API_URL } from "../Enum/EnvironmentVariable";
import Axios from "axios";
import {GameRoomPolicyTypes} from "_Model/PusherRoom";
import { GameRoomPolicyTypes } from "_Model/PusherRoom";
export interface AdminApiData {
organizationSlug: string
worldSlug: string
roomSlug: string
mapUrlStart: string
tags: string[]
policy_type: number
userUuid: string
messages?: unknown[],
textures: CharacterTexture[]
organizationSlug: string;
worldSlug: string;
roomSlug: string;
mapUrlStart: string;
tags: string[];
policy_type: number;
userUuid: string;
messages?: unknown[];
textures: CharacterTexture[];
}
export interface MapDetailsData {
roomSlug: string,
mapUrl: string,
policy_type: GameRoomPolicyTypes,
tags: string[],
roomSlug: string;
mapUrl: string;
policy_type: GameRoomPolicyTypes;
tags: string[];
}
export interface AdminBannedData {
is_banned: boolean,
message: string
is_banned: boolean;
message: string;
}
export interface CharacterTexture {
id: number,
level: number,
url: string,
rights: string
id: number;
level: number;
url: string;
rights: string;
}
export interface FetchMemberDataByUuidResponse {
uuid: string;
tags: string[];
visitCardUrl: string|null;
visitCardUrl: string | null;
textures: CharacterTexture[];
messages: unknown[];
anonymous?: boolean;
}
class AdminApi {
async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise<MapDetailsData> {
async fetchMapDetails(
organizationSlug: string,
worldSlug: string,
roomSlug: string | undefined
): Promise<MapDetailsData> {
if (!ADMIN_API_URL) {
return Promise.reject(new Error('No admin backoffice set!'));
return Promise.reject(new Error("No admin backoffice set!"));
}
const params: { organizationSlug: string, worldSlug: string, roomSlug?: string } = {
const params: { organizationSlug: string; worldSlug: string; roomSlug?: string } = {
organizationSlug,
worldSlug
worldSlug,
};
if (roomSlug) {
params.roomSlug = roomSlug;
}
const res = await Axios.get(ADMIN_API_URL + '/api/map',
{
headers: {"Authorization": `${ADMIN_API_TOKEN}`},
params
}
)
const res = await Axios.get(ADMIN_API_URL + "/api/map", {
headers: { Authorization: `${ADMIN_API_TOKEN}` },
params,
});
return res.data;
}
async fetchMemberDataByUuid(uuid: string, roomId: string): Promise<FetchMemberDataByUuidResponse> {
if (!ADMIN_API_URL) {
return Promise.reject(new Error('No admin backoffice set!'));
return Promise.reject(new Error("No admin backoffice set!"));
}
const res = await Axios.get(ADMIN_API_URL+'/api/room/access',
{ params: {uuid, roomId}, headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
)
const res = await Axios.get(ADMIN_API_URL + "/api/room/access", {
params: { uuid, roomId },
headers: { Authorization: `${ADMIN_API_TOKEN}` },
});
return res.data;
}
async fetchMemberDataByToken(organizationMemberToken: string): Promise<AdminApiData> {
if (!ADMIN_API_URL) {
return Promise.reject(new Error('No admin backoffice set!'));
return Promise.reject(new Error("No admin backoffice set!"));
}
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
const res = await Axios.get(ADMIN_API_URL+'/api/login-url/'+organizationMemberToken,
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
)
const res = await Axios.get(ADMIN_API_URL + "/api/login-url/" + organizationMemberToken, {
headers: { Authorization: `${ADMIN_API_TOKEN}` },
});
return res.data;
}
async fetchCheckUserByToken(organizationMemberToken: string): Promise<AdminApiData> {
if (!ADMIN_API_URL) {
return Promise.reject(new Error('No admin backoffice set!'));
return Promise.reject(new Error("No admin backoffice set!"));
}
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
const res = await Axios.get(ADMIN_API_URL+'/api/check-user/'+organizationMemberToken,
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
)
const res = await Axios.get(ADMIN_API_URL + "/api/check-user/" + organizationMemberToken, {
headers: { Authorization: `${ADMIN_API_TOKEN}` },
});
return res.data;
}
reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string, reportWorldSlug: string) {
return Axios.post(`${ADMIN_API_URL}/api/report`, {
reportPlayer(
reportedUserUuid: string,
reportedUserComment: string,
reporterUserUuid: string,
reportWorldSlug: string
) {
return Axios.post(
`${ADMIN_API_URL}/api/report`,
{
reportedUserUuid,
reportedUserComment,
reporterUserUuid,
reportWorldSlug
reportWorldSlug,
},
{
headers: {"Authorization": `${ADMIN_API_TOKEN}`}
});
headers: { Authorization: `${ADMIN_API_TOKEN}` },
}
);
}
async verifyBanUser(organizationMemberToken: string, ipAddress: string, organization: string, world: string): Promise<AdminBannedData> {
async verifyBanUser(
organizationMemberToken: string,
ipAddress: string,
organization: string,
world: string
): Promise<AdminBannedData> {
if (!ADMIN_API_URL) {
return Promise.reject(new Error('No admin backoffice set!'));
return Promise.reject(new Error("No admin backoffice set!"));
}
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
return Axios.get(ADMIN_API_URL + '/api/check-moderate-user/'+organization+'/'+world+'?ipAddress='+ipAddress+'&token='+organizationMemberToken,
{headers: {"Authorization": `${ADMIN_API_TOKEN}`}}
return Axios.get(
ADMIN_API_URL +
"/api/check-moderate-user/" +
organization +
"/" +
world +
"?ipAddress=" +
ipAddress +
"&token=" +
organizationMemberToken,
{ headers: { Authorization: `${ADMIN_API_TOKEN}` } }
).then((data) => {
return data.data;
});

View file

@ -1,14 +1,14 @@
/**
* A class to get connections to the correct "api" server given a room name.
*/
import {RoomManagerClient} from "../Messages/generated/messages_grpc_pb";
import grpc from 'grpc';
import crypto from 'crypto';
import {API_URL} from "../Enum/EnvironmentVariable";
import { RoomManagerClient } from "../Messages/generated/messages_grpc_pb";
import grpc from "grpc";
import crypto from "crypto";
import { API_URL } from "../Enum/EnvironmentVariable";
import Debug from "debug";
const debug = Debug('apiClientRespository');
const debug = Debug("apiClientRespository");
class ApiClientRepository {
private roomManagerClients: RoomManagerClient[] = [];
@ -16,23 +16,26 @@ class ApiClientRepository {
public constructor(private apiUrls: string[]) {}
public async getClient(roomId: string): Promise<RoomManagerClient> {
const array = new Uint32Array(crypto.createHash('md5').update(roomId).digest());
const array = new Uint32Array(crypto.createHash("md5").update(roomId).digest());
const index = array[0] % this.apiUrls.length;
let client = this.roomManagerClients[index];
if (client === undefined) {
this.roomManagerClients[index] = client = new RoomManagerClient(this.apiUrls[index], grpc.credentials.createInsecure());
debug('Mapping room %s to API server %s', roomId, this.apiUrls[index])
this.roomManagerClients[index] = client = new RoomManagerClient(
this.apiUrls[index],
grpc.credentials.createInsecure()
);
debug("Mapping room %s to API server %s", roomId, this.apiUrls[index]);
}
return Promise.resolve(client);
}
public async getAllClients(): Promise<RoomManagerClient[]> {
return [await this.getClient('')];
return [await this.getClient("")];
}
}
const apiClientRepository = new ApiClientRepository(API_URL.split(','));
const apiClientRepository = new ApiClientRepository(API_URL.split(","));
export { apiClientRepository };

View file

@ -1,3 +1,3 @@
export const arrayIntersect = (array1: string[], array2: string[]) : boolean => {
return array1.filter(value => array2.includes(value)).length > 0;
}
export const arrayIntersect = (array1: string[], array2: string[]): boolean => {
return array1.filter((value) => array2.includes(value)).length > 0;
};

View file

@ -1,7 +1,7 @@
const EventEmitter = require('events');
const EventEmitter = require("events");
const clientJoinEvent = 'clientJoin';
const clientLeaveEvent = 'clientLeave';
const clientJoinEvent = "clientJoin";
const clientLeaveEvent = "clientLeave";
class ClientEventsEmitter extends EventEmitter {
emitClientJoin(clientUUid: string, roomId: string): void {
@ -11,7 +11,7 @@ class ClientEventsEmitter extends EventEmitter {
emitClientLeave(clientUUid: string, roomId: string): void {
this.emit(clientLeaveEvent, clientUUid, roomId);
}
registerToClientJoin(callback: (clientUUid: string, roomId: string) => void): void {
this.on(clientJoinEvent, callback);
}
@ -29,4 +29,4 @@ class ClientEventsEmitter extends EventEmitter {
}
}
export const clientEventsEmitter = new ClientEventsEmitter();
export const clientEventsEmitter = new ClientEventsEmitter();

View file

@ -1,6 +1,6 @@
import {CPU_OVERHEAT_THRESHOLD} from "../Enum/EnvironmentVariable";
import { CPU_OVERHEAT_THRESHOLD } from "../Enum/EnvironmentVariable";
function secNSec2ms(secNSec: Array<number>|number) {
function secNSec2ms(secNSec: Array<number> | number) {
if (Array.isArray(secNSec)) {
return secNSec[0] * 1000 + secNSec[1] / 1000000;
}
@ -12,17 +12,17 @@ class CpuTracker {
private overHeating: boolean = false;
constructor() {
let time = process.hrtime.bigint()
let usage = process.cpuUsage()
let time = process.hrtime.bigint();
let usage = process.cpuUsage();
setInterval(() => {
const elapTime = process.hrtime.bigint();
const elapUsage = process.cpuUsage(usage)
usage = process.cpuUsage()
const elapUsage = process.cpuUsage(usage);
usage = process.cpuUsage();
const elapTimeMS = elapTime - time;
const elapUserMS = secNSec2ms(elapUsage.user)
const elapSystMS = secNSec2ms(elapUsage.system)
this.cpuPercent = Math.round(100 * (elapUserMS + elapSystMS) / Number(elapTimeMS) * 1000000)
const elapUserMS = secNSec2ms(elapUsage.user);
const elapSystMS = secNSec2ms(elapUsage.system);
this.cpuPercent = Math.round(((100 * (elapUserMS + elapSystMS)) / Number(elapTimeMS)) * 1000000);
time = elapTime;

View file

@ -1,4 +1,4 @@
import {Counter, Gauge} from "prom-client";
import { Counter, Gauge } from "prom-client";
//this class should manage all the custom metrics used by prometheus
class GaugeManager {
@ -9,25 +9,25 @@ class GaugeManager {
constructor() {
this.nbClientsGauge = new Gauge({
name: 'workadventure_nb_sockets',
help: 'Number of connected sockets',
labelNames: [ ]
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' ]
name: "workadventure_nb_clients_per_room",
help: "Number of clients per room",
labelNames: ["room"],
});
this.nbGroupsPerRoomCounter = new Counter({
name: 'workadventure_counter_groups_per_room',
help: 'Counter of groups per room',
labelNames: [ 'room' ]
name: "workadventure_counter_groups_per_room",
help: "Counter of groups per room",
labelNames: ["room"],
});
this.nbGroupsPerRoomGauge = new Gauge({
name: 'workadventure_nb_groups_per_room',
help: 'Number of groups per room',
labelNames: [ 'room' ]
name: "workadventure_nb_groups_per_room",
help: "Number of groups per room",
labelNames: ["room"],
});
}
@ -42,13 +42,13 @@ class GaugeManager {
}
incNbGroupsPerRoomGauge(roomId: string): void {
this.nbGroupsPerRoomCounter.inc({ room: roomId })
this.nbGroupsPerRoomGauge.inc({ room: roomId })
this.nbGroupsPerRoomCounter.inc({ room: roomId });
this.nbGroupsPerRoomGauge.inc({ room: roomId });
}
decNbGroupsPerRoomGauge(roomId: string): void {
this.nbGroupsPerRoomGauge.dec({ room: roomId })
this.nbGroupsPerRoomGauge.dec({ room: roomId });
}
}
export const gaugeManager = new GaugeManager();
export const gaugeManager = new GaugeManager();

View file

@ -1,6 +1,6 @@
import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
import {BatchMessage, ErrorMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb";
import {WebSocket} from "uWebSockets.js";
import { ExSocketInterface } from "_Model/Websocket/ExSocketInterface";
import { BatchMessage, ErrorMessage, ServerToClientMessage, SubMessage } from "../Messages/generated/messages_pb";
import { WebSocket } from "uWebSockets.js";
export function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void {
socket.batchedMessages.addPayload(payload);
@ -33,4 +33,3 @@ export function emitError(Client: WebSocket, message: string): void {
}
console.warn(message);
}

View file

@ -1,73 +1,77 @@
import {ADMIN_API_URL, ALLOW_ARTILLERY, SECRET_KEY} from "../Enum/EnvironmentVariable";
import {uuid} from "uuidv4";
import { ADMIN_API_URL, ALLOW_ARTILLERY, SECRET_KEY } from "../Enum/EnvironmentVariable";
import { uuid } from "uuidv4";
import Jwt from "jsonwebtoken";
import {TokenInterface} from "../Controller/AuthenticateController";
import {adminApi, AdminBannedData} from "../Services/AdminApi";
import { TokenInterface } from "../Controller/AuthenticateController";
import { adminApi, AdminBannedData } from "../Services/AdminApi";
class JWTTokenManager {
public createJWTToken(userUuid: string) {
return Jwt.sign({userUuid: userUuid}, SECRET_KEY, {expiresIn: '200d'}); //todo: add a mechanic to refresh or recreate token
return Jwt.sign({ userUuid: userUuid }, SECRET_KEY, { expiresIn: "200d" }); //todo: add a mechanic to refresh or recreate token
}
public async getUserUuidFromToken(token: unknown, ipAddress?: string, room?: string): Promise<string> {
if (!token) {
throw new Error('An authentication error happened, a user tried to connect without a token.');
throw new Error("An authentication error happened, a user tried to connect without a token.");
}
if (typeof(token) !== "string") {
throw new Error('Token is expected to be a string');
if (typeof token !== "string") {
throw new Error("Token is expected to be a string");
}
if(token === 'test') {
if (token === "test") {
if (ALLOW_ARTILLERY) {
return uuid();
} else {
throw new Error("In order to perform a load-testing test on this environment, you must set the ALLOW_ARTILLERY environment variable to 'true'");
throw new Error(
"In order to perform a load-testing test on this environment, you must set the ALLOW_ARTILLERY environment variable to 'true'"
);
}
}
return new Promise<string>((resolve, reject) => {
Jwt.verify(token, SECRET_KEY, {},(err, tokenDecoded) => {
Jwt.verify(token, SECRET_KEY, {}, (err, tokenDecoded) => {
const tokenInterface = tokenDecoded as TokenInterface;
if (err) {
console.error('An authentication error happened, invalid JsonWebToken.', err);
reject(new Error('An authentication error happened, invalid JsonWebToken. ' + err.message));
console.error("An authentication error happened, invalid JsonWebToken.", err);
reject(new Error("An authentication error happened, invalid JsonWebToken. " + err.message));
return;
}
if (tokenDecoded === undefined) {
console.error('Empty token found.');
reject(new Error('Empty token found.'));
console.error("Empty token found.");
reject(new Error("Empty token found."));
return;
}
//verify token
if (!this.isValidToken(tokenInterface)) {
reject(new Error('Authentication error, invalid token structure.'));
reject(new Error("Authentication error, invalid token structure."));
return;
}
if (ADMIN_API_URL) {
//verify user in admin
let promise = new Promise((resolve) => resolve());
if(ipAddress && room) {
if (ipAddress && room) {
promise = this.verifyBanUser(tokenInterface.userUuid, ipAddress, room);
}
promise.then(() => {
adminApi.fetchCheckUserByToken(tokenInterface.userUuid).then(() => {
resolve(tokenInterface.userUuid);
}).catch((err) => {
//anonymous user
if (err.response && err.response.status && err.response.status === 404) {
resolve(tokenInterface.userUuid);
return;
}
promise
.then(() => {
adminApi
.fetchCheckUserByToken(tokenInterface.userUuid)
.then(() => {
resolve(tokenInterface.userUuid);
})
.catch((err) => {
//anonymous user
if (err.response && err.response.status && err.response.status === 404) {
resolve(tokenInterface.userUuid);
return;
}
reject(err);
});
})
.catch((err) => {
reject(err);
});
}).catch((err) => {
reject(err);
});
} else {
resolve(tokenInterface.userUuid);
}
@ -76,30 +80,32 @@ class JWTTokenManager {
}
private verifyBanUser(userUuid: string, ipAddress: string, room: string): Promise<AdminBannedData> {
const parts = room.split('/');
if (parts.length < 3 || parts[0] !== '@') {
const parts = room.split("/");
if (parts.length < 3 || parts[0] !== "@") {
return Promise.resolve({
is_banned: false,
message: ''
message: "",
});
}
const organization = parts[1];
const world = parts[2];
return adminApi.verifyBanUser(userUuid, ipAddress, organization, world).then((data: AdminBannedData) => {
if (data && data.is_banned) {
throw new Error('User was banned');
}
return data;
}).catch((err) => {
throw err;
});
return adminApi
.verifyBanUser(userUuid, ipAddress, organization, world)
.then((data: AdminBannedData) => {
if (data && data.is_banned) {
throw new Error("User was banned");
}
return data;
})
.catch((err) => {
throw err;
});
}
private isValidToken(token: object): token is TokenInterface {
return !(typeof((token as TokenInterface).userUuid) !== 'string');
return !(typeof (token as TokenInterface).userUuid !== "string");
}
}
export const jwtTokenManager = new JWTTokenManager();

View file

@ -1,5 +1,5 @@
import {PusherRoom} from "../Model/PusherRoom";
import {CharacterLayer, ExSocketInterface} from "../Model/Websocket/ExSocketInterface";
import { PusherRoom } from "../Model/PusherRoom";
import { CharacterLayer, ExSocketInterface } from "../Model/Websocket/ExSocketInterface";
import {
GroupDeleteMessage,
ItemEventMessage,
@ -31,21 +31,21 @@ import {
RefreshRoomMessage,
EmotePromptMessage,
} from "../Messages/generated/messages_pb";
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
import {JITSI_ISS, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
import {adminApi, CharacterTexture} from "./AdminApi";
import {emitInBatch} from "./IoSocketHelpers";
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
import { JITSI_ISS, SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable";
import { adminApi, CharacterTexture } from "./AdminApi";
import { emitInBatch } from "./IoSocketHelpers";
import Jwt from "jsonwebtoken";
import {JITSI_URL} from "../Enum/EnvironmentVariable";
import {clientEventsEmitter} from "./ClientEventsEmitter";
import {gaugeManager} from "./GaugeManager";
import {apiClientRepository} from "./ApiClientRepository";
import {GroupDescriptor, UserDescriptor, ZoneEventListener} from "_Model/Zone";
import { JITSI_URL } from "../Enum/EnvironmentVariable";
import { clientEventsEmitter } from "./ClientEventsEmitter";
import { gaugeManager } from "./GaugeManager";
import { apiClientRepository } from "./ApiClientRepository";
import { GroupDescriptor, UserDescriptor, ZoneEventListener } from "_Model/Zone";
import Debug from "debug";
import {ExAdminSocketInterface} from "_Model/Websocket/ExAdminSocketInterface";
import {WebSocket} from "uWebSockets.js";
import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface";
import { WebSocket } from "uWebSockets.js";
const debug = Debug('socket');
const debug = Debug("socket");
interface AdminSocketRoomsList {
[index: string]: number;
@ -55,12 +55,11 @@ interface AdminSocketUsersList {
}
export interface AdminSocketData {
rooms: AdminSocketRoomsList,
users: AdminSocketUsersList,
rooms: AdminSocketRoomsList;
users: AdminSocketUsersList;
}
export class SocketManager implements ZoneEventListener {
private rooms: Map<string, PusherRoom> = new Map<string, PusherRoom>();
private sockets: Map<number, ExSocketInterface> = new Map<number, ExSocketInterface>();
@ -78,47 +77,53 @@ export class SocketManager implements ZoneEventListener {
const adminRoomStream = apiClient.adminRoom();
client.adminConnection = adminRoomStream;
adminRoomStream.on('data', (message: ServerToAdminClientMessage) => {
if (message.hasUserjoinedroom()) {
const userJoinedRoomMessage = message.getUserjoinedroom() as UserJoinedRoomMessage;
if (!client.disconnecting) {
client.send(JSON.stringify({
type: 'MemberJoin',
data: {
uuid: userJoinedRoomMessage.getUuid(),
name: userJoinedRoomMessage.getName(),
ipAddress: userJoinedRoomMessage.getIpaddress(),
roomId: roomId,
}
}));
adminRoomStream
.on("data", (message: ServerToAdminClientMessage) => {
if (message.hasUserjoinedroom()) {
const userJoinedRoomMessage = message.getUserjoinedroom() as UserJoinedRoomMessage;
if (!client.disconnecting) {
client.send(
JSON.stringify({
type: "MemberJoin",
data: {
uuid: userJoinedRoomMessage.getUuid(),
name: userJoinedRoomMessage.getName(),
ipAddress: userJoinedRoomMessage.getIpaddress(),
roomId: roomId,
},
})
);
}
} else if (message.hasUserleftroom()) {
const userLeftRoomMessage = message.getUserleftroom() as UserLeftRoomMessage;
if (!client.disconnecting) {
client.send(
JSON.stringify({
type: "MemberLeave",
data: {
uuid: userLeftRoomMessage.getUuid(),
},
})
);
}
} else {
throw new Error("Unexpected admin message");
}
} else if (message.hasUserleftroom()) {
const userLeftRoomMessage = message.getUserleftroom() as UserLeftRoomMessage;
})
.on("end", () => {
console.warn("Admin connection lost to back server");
// Let's close the front connection if the back connection is closed. This way, we can retry connecting from the start.
if (!client.disconnecting) {
client.send(JSON.stringify({
type: 'MemberLeave',
data: {
uuid: userLeftRoomMessage.getUuid()
}
}));
this.closeWebsocketConnection(client, 1011, "Connection lost to back server");
}
} else {
throw new Error('Unexpected admin message');
}
}).on('end', () => {
console.warn('Admin connection lost to back server');
// Let's close the front connection if the back connection is closed. This way, we can retry connecting from the start.
if (!client.disconnecting) {
this.closeWebsocketConnection(client, 1011, 'Connection lost to back server');
}
console.log('A user left');
}).on('error', (err: Error) => {
console.error('Error in connection to back server:', err);
if (!client.disconnecting) {
this.closeWebsocketConnection(client, 1011, 'Error while connecting to back server');
}
});
console.log("A user left");
})
.on("error", (err: Error) => {
console.error("Error in connection to back server:", err);
if (!client.disconnecting) {
this.closeWebsocketConnection(client, 1011, "Error while connecting to back server");
}
});
const message = new AdminPusherToBackMessage();
message.setSubscribetoroom(roomId);
@ -126,14 +131,14 @@ export class SocketManager implements ZoneEventListener {
adminRoomStream.write(message);
}
leaveAdminRoom(socket : ExAdminSocketInterface) {
leaveAdminRoom(socket: ExAdminSocketInterface) {
if (socket.adminConnection) {
socket.adminConnection.end();
}
}
getAdminSocketDataFor(roomId:string): AdminSocketData {
throw new Error('Not reimplemented yet');
getAdminSocketDataFor(roomId: string): AdminSocketData {
throw new Error("Not reimplemented yet");
/*const data:AdminSocketData = {
rooms: {},
users: {},
@ -153,7 +158,6 @@ export class SocketManager implements ZoneEventListener {
async handleJoinRoom(client: ExSocketInterface): Promise<void> {
const viewport = client.viewport;
try {
const joinRoomMessage = new JoinRoomMessage();
joinRoomMessage.setUseruuid(client.userUuid);
joinRoomMessage.setIpaddress(client.IPAddress);
@ -176,46 +180,49 @@ export class SocketManager implements ZoneEventListener {
joinRoomMessage.addCharacterlayer(characterLayerMessage);
}
console.log('Calling joinRoom')
console.log("Calling joinRoom");
const apiClient = await apiClientRepository.getClient(client.roomId);
const streamToPusher = apiClient.joinRoom();
clientEventsEmitter.emitClientJoin(client.userUuid, client.roomId);
client.backConnection = streamToPusher;
streamToPusher.on('data', (message: ServerToClientMessage) => {
if (message.hasRoomjoinedmessage()) {
client.userId = (message.getRoomjoinedmessage() as RoomJoinedMessage).getCurrentuserid();
// TODO: do we need this.sockets anymore?
this.sockets.set(client.userId, client);
streamToPusher
.on("data", (message: ServerToClientMessage) => {
if (message.hasRoomjoinedmessage()) {
client.userId = (message.getRoomjoinedmessage() as RoomJoinedMessage).getCurrentuserid();
// TODO: do we need this.sockets anymore?
this.sockets.set(client.userId, client);
// If this is the first message sent, send back the viewport.
this.handleViewport(client, viewport);
}
if (message.hasRefreshroommessage()) {
const refreshMessage:RefreshRoomMessage = message.getRefreshroommessage() as unknown as RefreshRoomMessage;
this.refreshRoomData(refreshMessage.getRoomid(), refreshMessage.getVersionnumber())
}
// If this is the first message sent, send back the viewport.
this.handleViewport(client, viewport);
}
// Let's pass data over from the back to the client.
if (!client.disconnecting) {
client.send(message.serializeBinary().buffer, true);
}
}).on('end', () => {
console.warn('Connection lost to back server');
// Let's close the front connection if the back connection is closed. This way, we can retry connecting from the start.
if (!client.disconnecting) {
this.closeWebsocketConnection(client, 1011, 'Connection lost to back server');
}
console.log('A user left');
}).on('error', (err: Error) => {
console.error('Error in connection to back server:', err);
if (!client.disconnecting) {
this.closeWebsocketConnection(client, 1011, 'Error while connecting to back server');
}
});
if (message.hasRefreshroommessage()) {
const refreshMessage: RefreshRoomMessage =
message.getRefreshroommessage() as unknown as RefreshRoomMessage;
this.refreshRoomData(refreshMessage.getRoomid(), refreshMessage.getVersionnumber());
}
// Let's pass data over from the back to the client.
if (!client.disconnecting) {
client.send(message.serializeBinary().buffer, true);
}
})
.on("end", () => {
console.warn("Connection lost to back server");
// Let's close the front connection if the back connection is closed. This way, we can retry connecting from the start.
if (!client.disconnecting) {
this.closeWebsocketConnection(client, 1011, "Connection lost to back server");
}
console.log("A user left");
})
.on("error", (err: Error) => {
console.error("Error in connection to back server:", err);
if (!client.disconnecting) {
this.closeWebsocketConnection(client, 1011, "Error while connecting to back server");
}
});
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setJoinroommessage(joinRoomMessage);
@ -226,7 +233,7 @@ export class SocketManager implements ZoneEventListener {
}
}
private closeWebsocketConnection(client: ExSocketInterface|ExAdminSocketInterface, code: number, reason: string) {
private closeWebsocketConnection(client: ExSocketInterface | ExAdminSocketInterface, code: number, reason: string) {
client.disconnecting = true;
//this.leaveRoom(client);
//client.close();
@ -257,15 +264,13 @@ export class SocketManager implements ZoneEventListener {
const viewport = userMovesMessage.getViewport();
if (viewport === undefined) {
throw new Error('Missing viewport in UserMovesMessage');
throw new Error("Missing viewport in UserMovesMessage");
}
// Now, we need to listen to the correct viewport.
this.handleViewport(client, viewport.toObject())
this.handleViewport(client, viewport.toObject());
}
onEmote(emoteMessage: EmoteEventMessage, listener: ExSocketInterface): void {
const subMessage = new SubMessage();
subMessage.setEmoteeventmessage(emoteMessage);
@ -299,11 +304,16 @@ export class SocketManager implements ZoneEventListener {
try {
const reportedSocket = this.sockets.get(reportPlayerMessage.getReporteduserid());
if (!reportedSocket) {
throw 'reported socket user not found';
throw "reported socket user not found";
}
//TODO report user on admin application
//todo: move to back because this fail if the reported player is in another pusher.
await adminApi.reportPlayer(reportedSocket.userUuid, reportPlayerMessage.getReportcomment(), client.userUuid, client.roomId.split('/')[2])
//todo: move to back because this fail if the reported player is in another pusher.
await adminApi.reportPlayer(
reportedSocket.userUuid,
reportPlayerMessage.getReportcomment(),
client.userUuid,
client.roomId.split("/")[2]
);
} catch (e) {
console.error('An error occurred on "handleReportMessage"');
console.error(e);
@ -325,14 +335,14 @@ export class SocketManager implements ZoneEventListener {
}
private searchClientByIdOrFail(userId: number): ExSocketInterface {
const client: ExSocketInterface|undefined = this.sockets.get(userId);
const client: ExSocketInterface | undefined = this.sockets.get(userId);
if (client === undefined) {
throw new Error("Could not find user with id " + userId);
}
return client;
}
leaveRoom(socket : ExSocketInterface) {
leaveRoom(socket: ExSocketInterface) {
// leave previous room and world
try {
if (socket.roomId) {
@ -340,15 +350,15 @@ export class SocketManager implements ZoneEventListener {
//user leaves room
const room: PusherRoom | undefined = this.rooms.get(socket.roomId);
if (room) {
debug('Leaving room %s.', socket.roomId);
debug("Leaving room %s.", socket.roomId);
room.leave(socket);
if (room.isEmpty()) {
this.rooms.delete(socket.roomId);
debug('Room %s is empty. Deleting.', socket.roomId);
debug("Room %s is empty. Deleting.", socket.roomId);
}
} else {
console.error('Could not find the GameRoom the user is leaving!');
console.error("Could not find the GameRoom the user is leaving!");
}
//user leave previous room
//Client.leave(Client.roomId);
@ -356,7 +366,7 @@ export class SocketManager implements ZoneEventListener {
//delete Client.roomId;
this.sockets.delete(socket.userId);
clientEventsEmitter.emitClientLeave(socket.userUuid, socket.roomId);
console.log('A user left (', this.sockets.size, ' connected users)');
console.log("A user left (", this.sockets.size, " connected users)");
}
}
} finally {
@ -368,27 +378,27 @@ export class SocketManager implements ZoneEventListener {
async getOrCreateRoom(roomId: string): Promise<PusherRoom> {
//check and create new world for a room
let world = this.rooms.get(roomId)
if(world === undefined){
let world = this.rooms.get(roomId);
if (world === undefined) {
world = new PusherRoom(roomId, this);
if (!world.public) {
await this.updateRoomWithAdminData(world);
}
this.rooms.set(roomId, world);
}
return Promise.resolve(world)
return Promise.resolve(world);
}
public async updateRoomWithAdminData(world: PusherRoom): Promise<void> {
const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug)
const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug);
world.tags = data.tags;
world.policyType = Number(data.policy_type);
}
emitPlayGlobalMessage(client: ExSocketInterface, playglobalmessage: PlayGlobalMessage) {
if (!client.tags.includes('admin')) {
if (!client.tags.includes("admin")) {
//In case of xss injection, we just kill the connection.
throw 'Client is not an admin!';
throw "Client is not an admin!";
}
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setPlayglobalmessage(playglobalmessage);
@ -399,44 +409,48 @@ export class SocketManager implements ZoneEventListener {
public getWorlds(): Map<string, PusherRoom> {
return this.rooms;
}
searchClientByUuid(uuid: string): ExSocketInterface | null {
for(const socket of this.sockets.values()){
if(socket.userUuid === uuid){
for (const socket of this.sockets.values()) {
if (socket.userUuid === uuid) {
return socket;
}
}
return null;
}
public handleQueryJitsiJwtMessage(client: ExSocketInterface, queryJitsiJwtMessage: QueryJitsiJwtMessage) {
try {
const room = queryJitsiJwtMessage.getJitsiroom();
const tag = queryJitsiJwtMessage.getTag(); // FIXME: this is not secure. We should load the JSON for the current room and check rights associated to room instead.
if (SECRET_JITSI_KEY === '') {
throw new Error('You must set the SECRET_JITSI_KEY key to the secret to generate JWT tokens for Jitsi.');
if (SECRET_JITSI_KEY === "") {
throw new Error(
"You must set the SECRET_JITSI_KEY key to the secret to generate JWT tokens for Jitsi."
);
}
// Let's see if the current client has
const isAdmin = client.tags.includes(tag);
const jwt = Jwt.sign({
"aud": "jitsi",
"iss": JITSI_ISS,
"sub": JITSI_URL,
"room": room,
"moderator": isAdmin
}, SECRET_JITSI_KEY, {
expiresIn: '1d',
algorithm: "HS256",
header:
{
"alg": "HS256",
"typ": "JWT"
}
});
const jwt = Jwt.sign(
{
aud: "jitsi",
iss: JITSI_ISS,
sub: JITSI_URL,
room: room,
moderator: isAdmin,
},
SECRET_JITSI_KEY,
{
expiresIn: "1d",
algorithm: "HS256",
header: {
alg: "HS256",
typ: "JWT",
},
}
);
const sendJitsiJwtMessage = new SendJitsiJwtMessage();
sendJitsiJwtMessage.setJitsiroom(room);
@ -447,7 +461,7 @@ export class SocketManager implements ZoneEventListener {
client.send(serverToClientMessage.serializeBinary().buffer, true);
} catch (e) {
console.error('An error occured while generating the Jitsi JWT token: ', e);
console.error("An error occured while generating the Jitsi JWT token: ", e);
}
}
@ -471,7 +485,7 @@ export class SocketManager implements ZoneEventListener {
backAdminMessage.setType(type);
backConnection.sendAdminMessage(backAdminMessage, (error) => {
if (error !== null) {
console.error('Error while sending admin message', error);
console.error("Error while sending admin message", error);
}
});
}
@ -496,7 +510,7 @@ export class SocketManager implements ZoneEventListener {
banMessage.setType(type);
backConnection.ban(banMessage, (error) => {
if (error !== null) {
console.error('Error while sending admin message', error);
console.error("Error while sending admin message", error);
}
});
}
@ -504,25 +518,28 @@ export class SocketManager implements ZoneEventListener {
/**
* Merges the characterLayers received from the front (as an array of string) with the custom textures from the back.
*/
static mergeCharacterLayersAndCustomTextures(characterLayers: string[], memberTextures: CharacterTexture[]): CharacterLayer[] {
static mergeCharacterLayersAndCustomTextures(
characterLayers: string[],
memberTextures: CharacterTexture[]
): CharacterLayer[] {
const characterLayerObjs: CharacterLayer[] = [];
for (const characterLayer of characterLayers) {
if (characterLayer.startsWith('customCharacterTexture')) {
if (characterLayer.startsWith("customCharacterTexture")) {
const customCharacterLayerId: number = +characterLayer.substr(22);
for (const memberTexture of memberTextures) {
if (memberTexture.id == customCharacterLayerId) {
characterLayerObjs.push({
name: characterLayer,
url: memberTexture.url
})
url: memberTexture.url,
});
break;
}
}
} else {
characterLayerObjs.push({
name: characterLayer,
url: undefined
})
url: undefined,
});
}
}
return characterLayerObjs;
@ -572,7 +589,7 @@ export class SocketManager implements ZoneEventListener {
emitInBatch(listener, subMessage);
}
public emitWorldFullMessage(client: WebSocket) {
const errorMessage = new WorldFullMessage();
@ -594,9 +611,9 @@ export class SocketManager implements ZoneEventListener {
private refreshRoomData(roomId: string, versionNumber: number): void {
const room = this.rooms.get(roomId);
//this function is run for every users connected to the room, so we need to make sure the room wasn't already refreshed.
//this function is run for every users connected to the room, so we need to make sure the room wasn't already refreshed.
if (!room || !room.needsUpdate(versionNumber)) return;
this.updateRoomWithAdminData(room);
}