diff --git a/back/src/App.ts b/back/src/App.ts index 4bcc56ba..a6c42abb 100644 --- a/back/src/App.ts +++ b/back/src/App.ts @@ -1,7 +1,7 @@ // lib/app.ts -import {PrometheusController} from "./Controller/PrometheusController"; -import {DebugController} from "./Controller/DebugController"; -import {App as uwsApp} from "./Server/sifrr.server"; +import { PrometheusController } from "./Controller/PrometheusController"; +import { DebugController } from "./Controller/DebugController"; +import { App as uwsApp } from "./Server/sifrr.server"; class App { public app: uwsApp; diff --git a/back/src/Controller/BaseController.ts b/back/src/Controller/BaseController.ts index 93c17ab4..dc510d6c 100644 --- a/back/src/Controller/BaseController.ts +++ b/back/src/Controller/BaseController.ts @@ -1,10 +1,9 @@ -import {HttpResponse} from "uWebSockets.js"; - +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', '*'); + 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", "*"); } } diff --git a/back/src/Controller/DebugController.ts b/back/src/Controller/DebugController.ts index 509d8b2f..b7f037fd 100644 --- a/back/src/Controller/DebugController.ts +++ b/back/src/Controller/DebugController.ts @@ -1,53 +1,54 @@ -import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable"; -import {stringify} from "circular-json"; -import {HttpRequest, HttpResponse} from "uWebSockets.js"; -import { parse } from 'query-string'; -import {App} from "../Server/sifrr.server"; -import {socketManager} from "../Services/SocketManager"; +import { ADMIN_API_TOKEN } from "../Enum/EnvironmentVariable"; +import { stringify } from "circular-json"; +import { HttpRequest, HttpResponse } from "uWebSockets.js"; +import { parse } from "query-string"; +import { App } from "../Server/sifrr.server"; +import { socketManager } from "../Services/SocketManager"; export class DebugController { - constructor(private App : App) { + constructor(private App: App) { this.getDump(); } - - getDump(){ + getDump() { this.App.get("/dump", (res: HttpResponse, req: HttpRequest) => { const query = parse(req.getQuery()); if (query.token !== ADMIN_API_TOKEN) { - return res.status(401).send('Invalid token sent!'); + return res.status(401).send("Invalid token sent!"); } - return res.writeStatus('200 OK').writeHeader('Content-Type', 'application/json').end(stringify( - socketManager.getWorlds(), - (key: unknown, value: unknown) => { - if (key === 'listeners') { - return 'Listeners'; - } - if (key === 'socket') { - return 'Socket'; - } - if (key === 'batchedMessages') { - return 'BatchedMessages'; - } - if(value instanceof Map) { - const obj: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any - for (const [mapKey, mapValue] of value.entries()) { - obj[mapKey] = mapValue; + return res + .writeStatus("200 OK") + .writeHeader("Content-Type", "application/json") + .end( + stringify(socketManager.getWorlds(), (key: unknown, value: unknown) => { + if (key === "listeners") { + return "Listeners"; } - return obj; - } else if(value instanceof Set) { + if (key === "socket") { + return "Socket"; + } + if (key === "batchedMessages") { + return "BatchedMessages"; + } + if (value instanceof Map) { + const obj: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any + for (const [mapKey, mapValue] of value.entries()) { + obj[mapKey] = mapValue; + } + return obj; + } else if (value instanceof Set) { const obj: Array = []; for (const [setKey, setValue] of value.entries()) { obj.push(setValue); } return obj; - } else { - return value; - } - } - )); + } else { + return value; + } + }) + ); }); } } diff --git a/back/src/Controller/PrometheusController.ts b/back/src/Controller/PrometheusController.ts index e854cf43..3ab3d33f 100644 --- a/back/src/Controller/PrometheusController.ts +++ b/back/src/Controller/PrometheusController.ts @@ -1,7 +1,7 @@ -import {App} from "../Server/sifrr.server"; -import {HttpRequest, HttpResponse} from "uWebSockets.js"; -const register = require('prom-client').register; -const collectDefaultMetrics = require('prom-client').collectDefaultMetrics; +import { App } from "../Server/sifrr.server"; +import { HttpRequest, HttpResponse } from "uWebSockets.js"; +const register = require("prom-client").register; +const collectDefaultMetrics = require("prom-client").collectDefaultMetrics; export class PrometheusController { constructor(private App: App) { @@ -14,7 +14,7 @@ export class PrometheusController { } private metrics(res: HttpResponse, req: HttpRequest): void { - res.writeHeader('Content-Type', register.contentType); + res.writeHeader("Content-Type", register.contentType); res.end(register.metrics()); } } diff --git a/back/src/Enum/EnvironmentVariable.ts b/back/src/Enum/EnvironmentVariable.ts index 81693a98..19eddd3e 100644 --- a/back/src/Enum/EnvironmentVariable.ts +++ b/back/src/Enum/EnvironmentVariable.ts @@ -1,17 +1,17 @@ const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIMUM_DISTANCE) : 64; const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48; -const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == 'true' : false; -const ADMIN_API_URL = process.env.ADMIN_API_URL || ''; -const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || 'myapitoken'; +const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == "true" : false; +const ADMIN_API_URL = process.env.ADMIN_API_URL || ""; +const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "myapitoken"; const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80; -const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL; -const JITSI_ISS = process.env.JITSI_ISS || ''; -const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || ''; -const HTTP_PORT = parseInt(process.env.HTTP_PORT || '8080') || 8080; -const GRPC_PORT = parseInt(process.env.GRPC_PORT || '50051') || 50051; +const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL; +const JITSI_ISS = process.env.JITSI_ISS || ""; +const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || ""; +const HTTP_PORT = parseInt(process.env.HTTP_PORT || "8080") || 8080; +const GRPC_PORT = parseInt(process.env.GRPC_PORT || "50051") || 50051; export const SOCKET_IDLE_TIMER = parseInt(process.env.SOCKET_IDLE_TIMER as string) || 30; // maximum time (in second) without activity before a socket is closed -export const TURN_STATIC_AUTH_SECRET = process.env.TURN_STATIC_AUTH_SECRET || ''; -export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || '4'); +export const TURN_STATIC_AUTH_SECRET = process.env.TURN_STATIC_AUTH_SECRET || ""; +export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || "4"); export { MINIMUM_DISTANCE, @@ -24,5 +24,5 @@ export { CPU_OVERHEAT_THRESHOLD, JITSI_URL, JITSI_ISS, - SECRET_JITSI_KEY -} + SECRET_JITSI_KEY, +}; diff --git a/back/src/Model/Admin.ts b/back/src/Model/Admin.ts index 29b53385..93396fa8 100644 --- a/back/src/Model/Admin.ts +++ b/back/src/Model/Admin.ts @@ -1,15 +1,12 @@ import { ServerToAdminClientMessage, - UserJoinedRoomMessage, UserLeftRoomMessage + UserJoinedRoomMessage, + UserLeftRoomMessage, } from "../Messages/generated/messages_pb"; -import {AdminSocket} from "../RoomManager"; - +import { AdminSocket } from "../RoomManager"; export class Admin { - public constructor( - private readonly socket: AdminSocket - ) { - } + public constructor(private readonly socket: AdminSocket) {} public sendUserJoin(uuid: string, name: string, ip: string): void { const serverToAdminClientMessage = new ServerToAdminClientMessage(); @@ -24,7 +21,7 @@ export class Admin { this.socket.write(serverToAdminClientMessage); } - public sendUserLeft(uuid: string/*, name: string, ip: string*/): void { + public sendUserLeft(uuid: string /*, name: string, ip: string*/): void { const serverToAdminClientMessage = new ServerToAdminClientMessage(); const userLeftRoomMessage = new UserLeftRoomMessage(); diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 53d0a855..020f4c29 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -1,16 +1,16 @@ -import {PointInterface} from "./Websocket/PointInterface"; -import {Group} from "./Group"; -import {User, UserSocket} from "./User"; -import {PositionInterface} from "_Model/PositionInterface"; -import {EmoteCallback, EntersCallback, LeavesCallback, MovesCallback} from "_Model/Zone"; -import {PositionNotifier} from "./PositionNotifier"; -import {Movable} from "_Model/Movable"; -import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "./RoomIdentifier"; -import {arrayIntersect} from "../Services/ArrayHelper"; -import {EmoteEventMessage, JoinRoomMessage} from "../Messages/generated/messages_pb"; -import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils"; -import {ZoneSocket} from "src/RoomManager"; -import {Admin} from "../Model/Admin"; +import { PointInterface } from "./Websocket/PointInterface"; +import { Group } from "./Group"; +import { User, UserSocket } from "./User"; +import { PositionInterface } from "_Model/PositionInterface"; +import { EmoteCallback, EntersCallback, LeavesCallback, MovesCallback } from "_Model/Zone"; +import { PositionNotifier } from "./PositionNotifier"; +import { Movable } from "_Model/Movable"; +import { extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous } from "./RoomIdentifier"; +import { arrayIntersect } from "../Services/ArrayHelper"; +import { EmoteEventMessage, JoinRoomMessage } from "../Messages/generated/messages_pb"; +import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils"; +import { ZoneSocket } from "src/RoomManager"; +import { Admin } from "../Model/Admin"; export type ConnectCallback = (user: User, group: Group) => void; export type DisconnectCallback = (user: User, group: Group) => void; @@ -39,33 +39,33 @@ export class GameRoom { private readonly positionNotifier: PositionNotifier; public readonly roomId: string; public readonly roomSlug: string; - public readonly worldSlug: string = ''; - public readonly organizationSlug: string = ''; - private versionNumber:number = 1; + public readonly worldSlug: string = ""; + public readonly organizationSlug: string = ""; + private versionNumber: number = 1; private nextUserId: number = 1; - constructor(roomId: string, - connectCallback: ConnectCallback, - disconnectCallback: DisconnectCallback, - minDistance: number, - groupRadius: number, - onEnters: EntersCallback, - onMoves: MovesCallback, - onLeaves: LeavesCallback, - onEmote: EmoteCallback, + constructor( + roomId: string, + connectCallback: ConnectCallback, + disconnectCallback: DisconnectCallback, + minDistance: number, + groupRadius: number, + onEnters: EntersCallback, + onMoves: MovesCallback, + onLeaves: LeavesCallback, + onEmote: EmoteCallback ) { this.roomId = roomId; if (isRoomAnonymous(roomId)) { this.roomSlug = extractRoomSlugPublicRoomId(this.roomId); } else { - const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId(this.roomId); + const { organizationSlug, worldSlug, roomSlug } = extractDataFromPrivateRoomId(this.roomId); this.roomSlug = roomSlug; this.organizationSlug = organizationSlug; this.worldSlug = worldSlug; } - this.users = new Map(); this.usersByUuid = new Map(); this.admins = new Set(); @@ -86,21 +86,22 @@ export class GameRoom { return this.users; } - public getUserByUuid(uuid: string): User|undefined { + public getUserByUuid(uuid: string): User | undefined { return this.usersByUuid.get(uuid); } - public getUserById(id: number): User|undefined { + public getUserById(id: number): User | undefined { return this.users.get(id); } - - public join(socket : UserSocket, joinRoomMessage: JoinRoomMessage): User { + + public join(socket: UserSocket, joinRoomMessage: JoinRoomMessage): User { const positionMessage = joinRoomMessage.getPositionmessage(); if (positionMessage === undefined) { - throw new Error('Missing position message'); + throw new Error("Missing position message"); } const position = ProtobufUtils.toPointInterface(positionMessage); - const user = new User(this.nextUserId, + const user = new User( + this.nextUserId, joinRoomMessage.getUseruuid(), joinRoomMessage.getIpaddress(), position, @@ -126,12 +127,12 @@ export class GameRoom { return user; } - public leave(user : User){ + public leave(user: User) { const userObj = this.users.get(user.id); if (userObj === undefined) { - console.warn('User ', user.id, 'does not belong to this game room! It should!'); + console.warn("User ", user.id, "does not belong to this game room! It should!"); } - if (userObj !== undefined && typeof userObj.group !== 'undefined') { + if (userObj !== undefined && typeof userObj.group !== "undefined") { this.leaveGroup(userObj); } this.users.delete(user.id); @@ -143,7 +144,7 @@ export class GameRoom { // Notify admins for (const admin of this.admins) { - admin.sendUserLeft(user.uuid/*, user.name, user.IPAddress*/); + admin.sendUserLeft(user.uuid /*, user.name, user.IPAddress*/); } } @@ -151,7 +152,7 @@ export class GameRoom { return this.users.size === 0 && this.admins.size === 0; } - public updatePosition(user : User, userPosition: PointInterface): void { + public updatePosition(user: User, userPosition: PointInterface): void { user.setPosition(userPosition); this.updateUserGroup(user); @@ -173,22 +174,24 @@ export class GameRoom { return; } - const closestItem: User|Group|null = this.searchClosestAvailableUserOrGroup(user); + const closestItem: User | Group | null = this.searchClosestAvailableUserOrGroup(user); if (closestItem !== null) { if (closestItem instanceof Group) { // Let's join the group! closestItem.join(user); } else { - const closestUser : User = closestItem; - const group: Group = new Group(this.roomId,[ - user, - closestUser - ], this.connectCallback, this.disconnectCallback, this.positionNotifier); + const closestUser: User = closestItem; + const group: Group = new Group( + this.roomId, + [user, closestUser], + this.connectCallback, + this.disconnectCallback, + this.positionNotifier + ); this.groups.add(group); } } - } else { // If the user is part of a group: // should he leave the group? @@ -229,7 +232,9 @@ export class GameRoom { this.positionNotifier.leave(group); group.destroy(); if (!this.groups.has(group)) { - throw new Error("Could not find group "+group.getId()+" referenced by user "+user.id+" in World."); + throw new Error( + "Could not find group " + group.getId() + " referenced by user " + user.id + " in World." + ); } this.groups.delete(group); //todo: is the group garbage collected? @@ -247,16 +252,15 @@ export class GameRoom { * OR * - close enough to a group (distance <= groupRadius) */ - private searchClosestAvailableUserOrGroup(user: User): User|Group|null - { + private searchClosestAvailableUserOrGroup(user: User): User | Group | null { let minimumDistanceFound: number = Math.max(this.minDistance, this.groupRadius); let matchingItem: User | Group | null = null; this.users.forEach((currentUser, userId) => { // Let's only check users that are not part of a group - if (typeof currentUser.group !== 'undefined') { + if (typeof currentUser.group !== "undefined") { return; } - if(currentUser === user) { + if (currentUser === user) { return; } if (currentUser.silent) { @@ -265,7 +269,7 @@ export class GameRoom { const distance = GameRoom.computeDistance(user, currentUser); // compute distance between peers. - if(distance <= minimumDistanceFound && distance <= this.minDistance) { + if (distance <= minimumDistanceFound && distance <= this.minDistance) { minimumDistanceFound = distance; matchingItem = currentUser; } @@ -276,7 +280,7 @@ export class GameRoom { return; } const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), group.getPosition()); - if(distance <= minimumDistanceFound && distance <= this.groupRadius) { + if (distance <= minimumDistanceFound && distance <= this.groupRadius) { minimumDistanceFound = distance; matchingItem = group; } @@ -285,15 +289,15 @@ export class GameRoom { return matchingItem; } - public static computeDistance(user1: User, user2: User): number - { + public static computeDistance(user1: User, user2: User): number { const user1Position = user1.getPosition(); const user2Position = user2.getPosition(); - return Math.sqrt(Math.pow(user2Position.x - user1Position.x, 2) + Math.pow(user2Position.y - user1Position.y, 2)); + return Math.sqrt( + Math.pow(user2Position.x - user1Position.x, 2) + Math.pow(user2Position.y - user1Position.y, 2) + ); } - public static computeDistanceBetweenPositions(position1: PositionInterface, position2: PositionInterface): number - { + public static computeDistanceBetweenPositions(position1: PositionInterface, position2: PositionInterface): number { return Math.sqrt(Math.pow(position2.x - position1.x, 2) + Math.pow(position2.y - position1.y, 2)); } @@ -325,9 +329,9 @@ export class GameRoom { public adminLeave(admin: Admin): void { this.admins.delete(admin); } - + public incrementVersion(): number { - this.versionNumber++ + this.versionNumber++; return this.versionNumber; } diff --git a/back/src/Model/Group.ts b/back/src/Model/Group.ts index ffe7a78a..5a0f3be6 100644 --- a/back/src/Model/Group.ts +++ b/back/src/Model/Group.ts @@ -1,13 +1,12 @@ import { ConnectCallback, DisconnectCallback } from "./GameRoom"; import { User } from "./User"; -import {PositionInterface} from "_Model/PositionInterface"; -import {Movable} from "_Model/Movable"; -import {PositionNotifier} from "_Model/PositionNotifier"; -import {gaugeManager} from "../Services/GaugeManager"; -import {MAX_PER_GROUP} from "../Enum/EnvironmentVariable"; +import { PositionInterface } from "_Model/PositionInterface"; +import { Movable } from "_Model/Movable"; +import { PositionNotifier } from "_Model/PositionNotifier"; +import { gaugeManager } from "../Services/GaugeManager"; +import { MAX_PER_GROUP } from "../Enum/EnvironmentVariable"; export class Group implements Movable { - private static nextId: number = 1; private id: number; @@ -18,8 +17,13 @@ export class Group implements Movable { private wasDestroyed: boolean = false; private roomId: string; - - constructor(roomId: string, users: User[], private connectCallback: ConnectCallback, private disconnectCallback: DisconnectCallback, private positionNotifier: PositionNotifier) { + constructor( + roomId: string, + users: User[], + private connectCallback: ConnectCallback, + private disconnectCallback: DisconnectCallback, + private positionNotifier: PositionNotifier + ) { this.roomId = roomId; this.users = new Set(); this.id = Group.nextId; @@ -43,7 +47,7 @@ export class Group implements Movable { return Array.from(this.users.values()); } - getId() : number { + getId(): number { return this.id; } @@ -53,7 +57,7 @@ export class Group implements Movable { getPosition(): PositionInterface { return { x: this.x, - y: this.y + y: this.y, }; } @@ -83,7 +87,7 @@ export class Group implements Movable { if (oldX === undefined) { this.positionNotifier.enter(this); } else { - this.positionNotifier.updatePosition(this, {x, y}, {x: oldX, y: oldY}); + this.positionNotifier.updatePosition(this, { x, y }, { x: oldX, y: oldY }); } } @@ -95,19 +99,17 @@ export class Group implements Movable { return this.users.size <= 1; } - join(user: User): void - { + join(user: User): void { // Broadcast on the right event this.connectCallback(user, this); this.users.add(user); user.group = this; } - leave(user: User): void - { + leave(user: User): void { const success = this.users.delete(user); if (success === false) { - throw new Error("Could not find user "+user.id+" in the group "+this.id); + throw new Error("Could not find user " + user.id + " in the group " + this.id); } user.group = undefined; @@ -123,8 +125,7 @@ export class Group implements Movable { * Let's kick everybody out. * Usually used when there is only one user left. */ - destroy(): void - { + destroy(): void { if (this.hasEditedGauge) gaugeManager.decNbGroupsPerRoomGauge(this.roomId); for (const user of this.users) { this.leave(user); @@ -132,7 +133,7 @@ export class Group implements Movable { this.wasDestroyed = true; } - get getSize(){ + get getSize() { return this.users.size; } } diff --git a/back/src/Model/Movable.ts b/back/src/Model/Movable.ts index 173db0ae..ca586b7c 100644 --- a/back/src/Model/Movable.ts +++ b/back/src/Model/Movable.ts @@ -1,8 +1,8 @@ -import {PositionInterface} from "_Model/PositionInterface"; +import { PositionInterface } from "_Model/PositionInterface"; /** * A physical object that can be placed into a Zone */ export interface Movable { - getPosition(): PositionInterface + getPosition(): PositionInterface; } diff --git a/back/src/Model/PositionInterface.ts b/back/src/Model/PositionInterface.ts index d3b0dd47..65636759 100644 --- a/back/src/Model/PositionInterface.ts +++ b/back/src/Model/PositionInterface.ts @@ -1,4 +1,4 @@ export interface PositionInterface { - x: number, - y: number + x: number; + y: number; } diff --git a/back/src/Model/PositionNotifier.ts b/back/src/Model/PositionNotifier.ts index 275bf9d0..c34c1ef1 100644 --- a/back/src/Model/PositionNotifier.ts +++ b/back/src/Model/PositionNotifier.ts @@ -8,12 +8,12 @@ * The PositionNotifier is important for performance. It allows us to send the position of players only to a restricted * number of players around the current player. */ -import {EmoteCallback, EntersCallback, LeavesCallback, MovesCallback, Zone} from "./Zone"; -import {Movable} from "_Model/Movable"; -import {PositionInterface} from "_Model/PositionInterface"; -import {ZoneSocket} from "../RoomManager"; -import {User} from "_Model/User"; -import {EmoteEventMessage} from "../Messages/generated/messages_pb"; +import { EmoteCallback, EntersCallback, LeavesCallback, MovesCallback, Zone } from "./Zone"; +import { Movable } from "_Model/Movable"; +import { PositionInterface } from "_Model/PositionInterface"; +import { ZoneSocket } from "../RoomManager"; +import { User } from "_Model/User"; +import { EmoteEventMessage } from "../Messages/generated/messages_pb"; interface ZoneDescriptor { i: number; @@ -21,19 +21,24 @@ interface ZoneDescriptor { } export class PositionNotifier { - // TODO: we need a way to clean the zones if noone is in the zone and noone listening (to free memory!) private zones: Zone[][] = []; - constructor(private zoneWidth: number, private zoneHeight: number, private onUserEnters: EntersCallback, private onUserMoves: MovesCallback, private onUserLeaves: LeavesCallback, private onEmote: EmoteCallback) { - } + constructor( + private zoneWidth: number, + private zoneHeight: number, + private onUserEnters: EntersCallback, + private onUserMoves: MovesCallback, + private onUserLeaves: LeavesCallback, + private onEmote: EmoteCallback + ) {} private getZoneDescriptorFromCoordinates(x: number, y: number): ZoneDescriptor { return { i: Math.floor(x / this.zoneWidth), j: Math.floor(y / this.zoneHeight), - } + }; } public enter(thing: Movable): void { @@ -100,6 +105,5 @@ export class PositionNotifier { const zoneDesc = this.getZoneDescriptorFromCoordinates(user.getPosition().x, user.getPosition().y); const zone = this.getZone(zoneDesc.i, zoneDesc.j); zone.emitEmoteEvent(emoteEventMessage); - } } diff --git a/back/src/Model/RoomIdentifier.ts b/back/src/Model/RoomIdentifier.ts index 3ac62bca..d1de8800 100644 --- a/back/src/Model/RoomIdentifier.ts +++ b/back/src/Model/RoomIdentifier.ts @@ -1,30 +1,30 @@ //helper functions to parse room IDs export const isRoomAnonymous = (roomID: string): boolean => { - if (roomID.startsWith('_/')) { + if (roomID.startsWith("_/")) { return true; - } else if(roomID.startsWith('@/')) { + } else if (roomID.startsWith("@/")) { return false; } else { - throw new Error('Incorrect room ID: '+roomID); + throw new Error("Incorrect room ID: " + roomID); } -} +}; export const extractRoomSlugPublicRoomId = (roomId: string): string => { - const idParts = roomId.split('/'); - if (idParts.length < 3) throw new Error('Incorrect roomId: '+roomId); - return idParts.slice(2).join('/'); -} + const idParts = roomId.split("/"); + if (idParts.length < 3) throw new Error("Incorrect roomId: " + roomId); + return idParts.slice(2).join("/"); +}; export interface extractDataFromPrivateRoomIdResponse { organizationSlug: string; worldSlug: string; roomSlug: string; } export const extractDataFromPrivateRoomId = (roomId: string): extractDataFromPrivateRoomIdResponse => { - const idParts = roomId.split('/'); - if (idParts.length < 4) throw new Error('Incorrect roomId: '+roomId); + const idParts = roomId.split("/"); + if (idParts.length < 4) throw new Error("Incorrect roomId: " + roomId); const organizationSlug = idParts[1]; const worldSlug = idParts[2]; const roomSlug = idParts[3]; - return {organizationSlug, worldSlug, roomSlug} -} \ No newline at end of file + return { organizationSlug, worldSlug, roomSlug }; +}; diff --git a/back/src/Model/User.ts b/back/src/Model/User.ts index 4a3e75ec..186fb32a 100644 --- a/back/src/Model/User.ts +++ b/back/src/Model/User.ts @@ -1,11 +1,17 @@ import { Group } from "./Group"; import { PointInterface } from "./Websocket/PointInterface"; -import {Zone} from "_Model/Zone"; -import {Movable} from "_Model/Movable"; -import {PositionNotifier} from "_Model/PositionNotifier"; -import {ServerDuplexStream} from "grpc"; -import {BatchMessage, CompanionMessage, PusherToBackMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb"; -import {CharacterLayer} from "_Model/Websocket/CharacterLayer"; +import { Zone } from "_Model/Zone"; +import { Movable } from "_Model/Movable"; +import { PositionNotifier } from "_Model/PositionNotifier"; +import { ServerDuplexStream } from "grpc"; +import { + BatchMessage, + CompanionMessage, + PusherToBackMessage, + ServerToClientMessage, + SubMessage, +} from "../Messages/generated/messages_pb"; +import { CharacterLayer } from "_Model/Websocket/CharacterLayer"; export type UserSocket = ServerDuplexStream; @@ -22,7 +28,7 @@ export class User implements Movable { private positionNotifier: PositionNotifier, public readonly socket: UserSocket, public readonly tags: string[], - public readonly visitCardUrl: string|null, + public readonly visitCardUrl: string | null, public readonly name: string, public readonly characterLayers: CharacterLayer[], public readonly companion?: CompanionMessage @@ -42,9 +48,8 @@ export class User implements Movable { this.positionNotifier.updatePosition(this, position, oldPosition); } - private batchedMessages: BatchMessage = new BatchMessage(); - private batchTimeout: NodeJS.Timeout|null = null; + private batchTimeout: NodeJS.Timeout | null = null; public emitInBatch(payload: SubMessage): void { this.batchedMessages.addPayload(payload); diff --git a/back/src/Model/Websocket/CharacterLayer.ts b/back/src/Model/Websocket/CharacterLayer.ts index 13d838ee..3e428790 100644 --- a/back/src/Model/Websocket/CharacterLayer.ts +++ b/back/src/Model/Websocket/CharacterLayer.ts @@ -1,4 +1,4 @@ export interface CharacterLayer { - name: string, - url: string|undefined + name: string; + url: string | undefined; } diff --git a/back/src/Model/Websocket/ItemEventMessage.ts b/back/src/Model/Websocket/ItemEventMessage.ts index b1f9203e..1bb7f615 100644 --- a/back/src/Model/Websocket/ItemEventMessage.ts +++ b/back/src/Model/Websocket/ItemEventMessage.ts @@ -1,10 +1,11 @@ import * as tg from "generic-type-guard"; -export const isItemEventMessageInterface = - new tg.IsInterface().withProperties({ +export const isItemEventMessageInterface = new tg.IsInterface() + .withProperties({ itemId: tg.isNumber, event: tg.isString, state: tg.isUnknown, parameters: tg.isUnknown, - }).get(); + }) + .get(); export type ItemEventMessageInterface = tg.GuardedType; diff --git a/back/src/Model/Websocket/MessageUserPosition.ts b/back/src/Model/Websocket/MessageUserPosition.ts index ee43d58c..19b57d2e 100644 --- a/back/src/Model/Websocket/MessageUserPosition.ts +++ b/back/src/Model/Websocket/MessageUserPosition.ts @@ -1,7 +1,10 @@ -import {PointInterface} from "./PointInterface"; +import { PointInterface } from "./PointInterface"; -export class Point implements PointInterface{ - constructor(public x : number, public y : number, public direction : string = "none", public moving : boolean = false) { - } +export class Point implements PointInterface { + constructor( + public x: number, + public y: number, + public direction: string = "none", + public moving: boolean = false + ) {} } - diff --git a/back/src/Model/Websocket/PointInterface.ts b/back/src/Model/Websocket/PointInterface.ts index afb07a23..d7c7826e 100644 --- a/back/src/Model/Websocket/PointInterface.ts +++ b/back/src/Model/Websocket/PointInterface.ts @@ -7,11 +7,12 @@ import * as tg from "generic-type-guard"; readonly moving: boolean; }*/ -export const isPointInterface = - new tg.IsInterface().withProperties({ +export const isPointInterface = new tg.IsInterface() + .withProperties({ x: tg.isNumber, y: tg.isNumber, direction: tg.isString, - moving: tg.isBoolean - }).get(); + moving: tg.isBoolean, + }) + .get(); export type PointInterface = tg.GuardedType; diff --git a/back/src/Model/Websocket/ProtobufUtils.ts b/back/src/Model/Websocket/ProtobufUtils.ts index b85a4257..68817a4f 100644 --- a/back/src/Model/Websocket/ProtobufUtils.ts +++ b/back/src/Model/Websocket/ProtobufUtils.ts @@ -1,34 +1,33 @@ -import {PointInterface} from "./PointInterface"; +import { PointInterface } from "./PointInterface"; import { CharacterLayerMessage, ItemEventMessage, PointMessage, - PositionMessage + PositionMessage, } from "../../Messages/generated/messages_pb"; -import {CharacterLayer} from "_Model/Websocket/CharacterLayer"; +import { CharacterLayer } from "_Model/Websocket/CharacterLayer"; import Direction = PositionMessage.Direction; -import {ItemEventMessageInterface} from "_Model/Websocket/ItemEventMessage"; -import {PositionInterface} from "_Model/PositionInterface"; +import { ItemEventMessageInterface } from "_Model/Websocket/ItemEventMessage"; +import { PositionInterface } from "_Model/PositionInterface"; export class ProtobufUtils { - public static toPositionMessage(point: PointInterface): PositionMessage { let direction: Direction; switch (point.direction) { - case 'up': + case "up": direction = Direction.UP; break; - case 'down': + case "down": direction = Direction.DOWN; break; - case 'left': + case "left": direction = Direction.LEFT; break; - case 'right': + case "right": direction = Direction.RIGHT; break; default: - throw new Error('unexpected direction'); + throw new Error("unexpected direction"); } const position = new PositionMessage(); @@ -44,16 +43,16 @@ export class ProtobufUtils { let direction: string; switch (position.getDirection()) { case Direction.UP: - direction = 'up'; + direction = "up"; break; case Direction.DOWN: - direction = 'down'; + direction = "down"; break; case Direction.LEFT: - direction = 'left'; + direction = "left"; break; case Direction.RIGHT: - direction = 'right'; + direction = "right"; break; default: throw new Error("Unexpected direction"); @@ -82,7 +81,7 @@ export class ProtobufUtils { event: itemEventMessage.getEvent(), parameters: JSON.parse(itemEventMessage.getParametersjson()), state: JSON.parse(itemEventMessage.getStatejson()), - } + }; } public static toItemEventProtobuf(itemEvent: ItemEventMessageInterface): ItemEventMessage { @@ -96,7 +95,7 @@ export class ProtobufUtils { } public static toCharacterLayerMessages(characterLayers: CharacterLayer[]): CharacterLayerMessage[] { - return characterLayers.map(function(characterLayer): CharacterLayerMessage { + return characterLayers.map(function (characterLayer): CharacterLayerMessage { const message = new CharacterLayerMessage(); message.setName(characterLayer.name); if (characterLayer.url) { @@ -107,7 +106,7 @@ export class ProtobufUtils { } public static toCharacterLayerObjects(characterLayers: CharacterLayerMessage[]): CharacterLayer[] { - return characterLayers.map(function(characterLayer): CharacterLayer { + return characterLayers.map(function (characterLayer): CharacterLayer { const url = characterLayer.getUrl(); return { name: characterLayer.getName(), diff --git a/back/src/Model/Zone.ts b/back/src/Model/Zone.ts index ffb172bb..d236e489 100644 --- a/back/src/Model/Zone.ts +++ b/back/src/Model/Zone.ts @@ -1,35 +1,52 @@ -import {User} from "./User"; -import {PositionInterface} from "_Model/PositionInterface"; -import {Movable} from "./Movable"; -import {Group} from "./Group"; -import {ZoneSocket} from "../RoomManager"; -import {EmoteEventMessage} from "../Messages/generated/messages_pb"; +import { User } from "./User"; +import { PositionInterface } from "_Model/PositionInterface"; +import { Movable } from "./Movable"; +import { Group } from "./Group"; +import { ZoneSocket } from "../RoomManager"; +import { EmoteEventMessage } from "../Messages/generated/messages_pb"; -export type EntersCallback = (thing: Movable, fromZone: Zone|null, listener: ZoneSocket) => void; +export type EntersCallback = (thing: Movable, fromZone: Zone | null, listener: ZoneSocket) => void; export type MovesCallback = (thing: Movable, position: PositionInterface, listener: ZoneSocket) => void; -export type LeavesCallback = (thing: Movable, newZone: Zone|null, listener: ZoneSocket) => void; +export type LeavesCallback = (thing: Movable, newZone: Zone | null, listener: ZoneSocket) => void; export type EmoteCallback = (emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) => void; export class Zone { private things: Set = new Set(); private listeners: Set = new Set(); - - - constructor(private onEnters: EntersCallback, private onMoves: MovesCallback, private onLeaves: LeavesCallback, private onEmote: EmoteCallback, public readonly x: number, public readonly y: number) { } + + constructor( + private onEnters: EntersCallback, + private onMoves: MovesCallback, + private onLeaves: LeavesCallback, + private onEmote: EmoteCallback, + public readonly x: number, + public readonly y: number + ) {} /** * A user/thing leaves the zone */ - public leave(thing: Movable, newZone: Zone|null) { + public leave(thing: Movable, newZone: Zone | null) { const result = this.things.delete(thing); if (!result) { if (thing instanceof User) { - throw new Error('Could not find user in zone '+thing.id); + throw new Error("Could not find user in zone " + thing.id); } if (thing instanceof Group) { - throw new Error('Could not find group '+thing.getId()+' in zone ('+this.x+','+this.y+'). Position of group: ('+thing.getPosition().x+','+thing.getPosition().y+')'); + throw new Error( + "Could not find group " + + thing.getId() + + " in zone (" + + this.x + + "," + + this.y + + "). Position of group: (" + + thing.getPosition().x + + "," + + thing.getPosition().y + + ")" + ); } - } this.notifyLeft(thing, newZone); } @@ -37,13 +54,13 @@ export class Zone { /** * Notify listeners of this zone that this user/thing left */ - private notifyLeft(thing: Movable, newZone: Zone|null) { + private notifyLeft(thing: Movable, newZone: Zone | null) { for (const listener of this.listeners) { this.onLeaves(thing, newZone, listener); } } - public enter(thing: Movable, oldZone: Zone|null, position: PositionInterface) { + public enter(thing: Movable, oldZone: Zone | null, position: PositionInterface) { this.things.add(thing); this.notifyEnter(thing, oldZone, position); } @@ -51,13 +68,12 @@ export class Zone { /** * Notify listeners of this zone that this user entered */ - private notifyEnter(thing: Movable, oldZone: Zone|null, position: PositionInterface) { + private notifyEnter(thing: Movable, oldZone: Zone | null, position: PositionInterface) { for (const listener of this.listeners) { this.onEnters(thing, oldZone, listener); } } - public move(thing: Movable, position: PositionInterface) { if (!this.things.has(thing)) { this.things.add(thing); @@ -67,7 +83,7 @@ export class Zone { for (const listener of this.listeners) { //if (listener !== thing) { - this.onMoves(thing,position, listener); + this.onMoves(thing, position, listener); //} } } @@ -89,6 +105,5 @@ export class Zone { for (const listener of this.listeners) { this.onEmote(emoteEventMessage, listener); } - } } diff --git a/back/src/RoomManager.ts b/back/src/RoomManager.ts index 19266687..9aaf1edb 100644 --- a/back/src/RoomManager.ts +++ b/back/src/RoomManager.ts @@ -1,4 +1,4 @@ -import {IRoomManagerServer} from "./Messages/generated/messages_grpc_pb"; +import { IRoomManagerServer } from "./Messages/generated/messages_grpc_pb"; import { AdminGlobalMessage, AdminMessage, @@ -11,92 +11,114 @@ import { JoinRoomMessage, PlayGlobalMessage, PusherToBackMessage, - QueryJitsiJwtMessage, RefreshRoomPromptMessage, + QueryJitsiJwtMessage, + RefreshRoomPromptMessage, ServerToAdminClientMessage, ServerToClientMessage, SilentMessage, UserMovesMessage, - WebRtcSignalToServerMessage, WorldFullWarningToRoomMessage, - ZoneMessage + WebRtcSignalToServerMessage, + WorldFullWarningToRoomMessage, + ZoneMessage, } from "./Messages/generated/messages_pb"; -import {sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream} from "grpc"; -import {socketManager} from "./Services/SocketManager"; -import {emitError} from "./Services/MessageHelpers"; -import {User, UserSocket} from "./Model/User"; -import {GameRoom} from "./Model/GameRoom"; +import { sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream } from "grpc"; +import { socketManager } from "./Services/SocketManager"; +import { emitError } from "./Services/MessageHelpers"; +import { User, UserSocket } from "./Model/User"; +import { GameRoom } from "./Model/GameRoom"; import Debug from "debug"; -import {Admin} from "./Model/Admin"; +import { Admin } from "./Model/Admin"; -const debug = Debug('roommanager'); +const debug = Debug("roommanager"); export type AdminSocket = ServerDuplexStream; export type ZoneSocket = ServerWritableStream; const roomManager: IRoomManagerServer = { joinRoom: (call: UserSocket): void => { - console.log('joinRoom called'); + console.log("joinRoom called"); - let room: GameRoom|null = null; - let user: User|null = null; + let room: GameRoom | null = null; + let user: User | null = null; - call.on('data', (message: PusherToBackMessage) => { + call.on("data", (message: PusherToBackMessage) => { try { if (room === null || user === null) { if (message.hasJoinroommessage()) { - socketManager.handleJoinRoom(call, message.getJoinroommessage() as JoinRoomMessage).then(({room: gameRoom, user: myUser}) => { - if (call.writable) { - room = gameRoom; - user = myUser; - } else { - //Connexion may have been closed before the init was finished, so we have to manually disconnect the user. - socketManager.leaveRoom(gameRoom, myUser); - } - }); + socketManager + .handleJoinRoom(call, message.getJoinroommessage() as JoinRoomMessage) + .then(({ room: gameRoom, user: myUser }) => { + if (call.writable) { + room = gameRoom; + user = myUser; + } else { + //Connexion may have been closed before the init was finished, so we have to manually disconnect the user. + socketManager.leaveRoom(gameRoom, myUser); + } + }); } else { - throw new Error('The first message sent MUST be of type JoinRoomMessage'); + throw new Error("The first message sent MUST be of type JoinRoomMessage"); } } else { if (message.hasJoinroommessage()) { - throw new Error('Cannot call JoinRoomMessage twice!'); + throw new Error("Cannot call JoinRoomMessage twice!"); } else if (message.hasUsermovesmessage()) { - socketManager.handleUserMovesMessage(room, user, message.getUsermovesmessage() as UserMovesMessage); + socketManager.handleUserMovesMessage( + room, + user, + message.getUsermovesmessage() as UserMovesMessage + ); } else if (message.hasSilentmessage()) { socketManager.handleSilentMessage(room, user, message.getSilentmessage() as SilentMessage); } else if (message.hasItemeventmessage()) { socketManager.handleItemEvent(room, user, message.getItemeventmessage() as ItemEventMessage); } else if (message.hasWebrtcsignaltoservermessage()) { - socketManager.emitVideo(room, user, message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage); + socketManager.emitVideo( + room, + user, + message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage + ); } else if (message.hasWebrtcscreensharingsignaltoservermessage()) { - socketManager.emitScreenSharing(room, user, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage); + socketManager.emitScreenSharing( + room, + user, + message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage + ); } else if (message.hasPlayglobalmessage()) { socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage); - } else if (message.hasQueryjitsijwtmessage()){ - socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage); - } else if (message.hasEmotepromptmessage()){ - socketManager.handleEmoteEventMessage(room, user, message.getEmotepromptmessage() as EmotePromptMessage); - }else if (message.hasSendusermessage()) { + } else if (message.hasQueryjitsijwtmessage()) { + socketManager.handleQueryJitsiJwtMessage( + user, + message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage + ); + } else if (message.hasEmotepromptmessage()) { + socketManager.handleEmoteEventMessage( + room, + user, + message.getEmotepromptmessage() as EmotePromptMessage + ); + } else if (message.hasSendusermessage()) { const sendUserMessage = message.getSendusermessage(); - if(sendUserMessage !== undefined) { + if (sendUserMessage !== undefined) { socketManager.handlerSendUserMessage(user, sendUserMessage); } - }else if (message.hasBanusermessage()) { + } else if (message.hasBanusermessage()) { const banUserMessage = message.getBanusermessage(); - if(banUserMessage !== undefined) { + if (banUserMessage !== undefined) { socketManager.handlerBanUserMessage(room, user, banUserMessage); } } else { - throw new Error('Unhandled message type'); + throw new Error("Unhandled message type"); } } } catch (e) { emitError(call, e); call.end(); } - }); - call.on('end', () => { - debug('joinRoom ended'); + call.on("end", () => { + debug("joinRoom ended"); if (user !== null && room !== null) { socketManager.leaveRoom(room, user); } @@ -105,41 +127,40 @@ const roomManager: IRoomManagerServer = { user = null; }); - call.on('error', (err: Error) => { - console.error('An error occurred in joinRoom stream:', err); + call.on("error", (err: Error) => { + console.error("An error occurred in joinRoom stream:", err); }); - }, listenZone(call: ZoneSocket): void { - debug('listenZone called'); + debug("listenZone called"); const zoneMessage = call.request; socketManager.addZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY()); - call.on('cancelled', () => { - debug('listenZone cancelled'); + call.on("cancelled", () => { + debug("listenZone cancelled"); socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY()); call.end(); - }) - - call.on('close', () => { - debug('listenZone connection closed'); + }); + + call.on("close", () => { + debug("listenZone connection closed"); socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY()); - }).on('error', (e) => { - console.error('An error occurred in listenZone stream:', e); + }).on("error", (e) => { + console.error("An error occurred in listenZone stream:", e); socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY()); call.end(); }); }, adminRoom(call: AdminSocket): void { - console.log('adminRoom called'); + console.log("adminRoom called"); const admin = new Admin(call); - let room: GameRoom|null = null; + let room: GameRoom | null = null; - call.on('data', (message: AdminPusherToBackMessage) => { + call.on("data", (message: AdminPusherToBackMessage) => { try { if (room === null) { if (message.hasSubscribetoroom()) { @@ -148,18 +169,17 @@ const roomManager: IRoomManagerServer = { room = gameRoom; }); } else { - throw new Error('The first message sent MUST be of type JoinRoomMessage'); + throw new Error("The first message sent MUST be of type JoinRoomMessage"); } } } catch (e) { emitError(call, e); call.end(); } - }); - call.on('end', () => { - debug('joinRoom ended'); + call.on("end", () => { + debug("joinRoom ended"); if (room !== null) { socketManager.leaveAdminRoom(room, admin); } @@ -167,18 +187,21 @@ const roomManager: IRoomManagerServer = { room = null; }); - call.on('error', (err: Error) => { - console.error('An error occurred in joinAdminRoom stream:', err); + call.on("error", (err: Error) => { + console.error("An error occurred in joinAdminRoom stream:", err); }); }, sendAdminMessage(call: ServerUnaryCall, callback: sendUnaryData): void { - - socketManager.sendAdminMessage(call.request.getRoomid(), call.request.getRecipientuuid(), call.request.getMessage()); + socketManager.sendAdminMessage( + call.request.getRoomid(), + call.request.getRecipientuuid(), + call.request.getMessage() + ); callback(null, new EmptyMessage()); }, sendGlobalAdminMessage(call: ServerUnaryCall, callback: sendUnaryData): void { - throw new Error('Not implemented yet'); + throw new Error("Not implemented yet"); // TODO callback(null, new EmptyMessage()); }, @@ -192,14 +215,20 @@ const roomManager: IRoomManagerServer = { socketManager.sendAdminRoomMessage(call.request.getRoomid(), call.request.getMessage()); callback(null, new EmptyMessage()); }, - sendWorldFullWarningToRoom(call: ServerUnaryCall, callback: sendUnaryData): void { + sendWorldFullWarningToRoom( + call: ServerUnaryCall, + callback: sendUnaryData + ): void { socketManager.dispatchWorlFullWarning(call.request.getRoomid()); callback(null, new EmptyMessage()); }, - sendRefreshRoomPrompt(call: ServerUnaryCall, callback: sendUnaryData): void { + sendRefreshRoomPrompt( + call: ServerUnaryCall, + callback: sendUnaryData + ): void { socketManager.dispatchRoomRefresh(call.request.getRoomid()); callback(null, new EmptyMessage()); }, }; -export {roomManager}; +export { roomManager }; diff --git a/back/src/Server/server/app.ts b/back/src/Server/server/app.ts index 3b98a9b3..4c422d5c 100644 --- a/back/src/Server/server/app.ts +++ b/back/src/Server/server/app.ts @@ -1,13 +1,13 @@ -import { App as _App, AppOptions } from 'uWebSockets.js'; -import BaseApp from './baseapp'; -import { extend } from './utils'; -import { UwsApp } from './types'; +import { App as _App, AppOptions } from "uWebSockets.js"; +import BaseApp from "./baseapp"; +import { extend } from "./utils"; +import { UwsApp } from "./types"; class App extends (_App) { - constructor(options: AppOptions = {}) { - super(options); // eslint-disable-line constructor-super - extend(this, new BaseApp()); - } + constructor(options: AppOptions = {}) { + super(options); // eslint-disable-line constructor-super + extend(this, new BaseApp()); + } } export default App; diff --git a/back/src/Server/server/baseapp.ts b/back/src/Server/server/baseapp.ts index accd8a99..6d973ac7 100644 --- a/back/src/Server/server/baseapp.ts +++ b/back/src/Server/server/baseapp.ts @@ -1,116 +1,109 @@ -import { Readable } from 'stream'; -import { us_listen_socket_close, TemplatedApp, HttpResponse, HttpRequest } from 'uWebSockets.js'; +import { Readable } from "stream"; +import { us_listen_socket_close, TemplatedApp, HttpResponse, HttpRequest } from "uWebSockets.js"; -import formData from './formdata'; -import { stob } from './utils'; -import { Handler } from './types'; -import {join} from "path"; +import formData from "./formdata"; +import { stob } from "./utils"; +import { Handler } from "./types"; +import { join } from "path"; -const contTypes = ['application/x-www-form-urlencoded', 'multipart/form-data']; +const contTypes = ["application/x-www-form-urlencoded", "multipart/form-data"]; const noOp = () => true; const handleBody = (res: HttpResponse, req: HttpRequest) => { - const contType = req.getHeader('content-type'); + const contType = req.getHeader("content-type"); - res.bodyStream = function() { - const stream = new Readable(); - stream._read = noOp; // eslint-disable-line @typescript-eslint/unbound-method + res.bodyStream = function () { + const stream = new Readable(); + stream._read = noOp; // eslint-disable-line @typescript-eslint/unbound-method - this.onData((ab: ArrayBuffer, isLast: boolean) => { - // uint and then slicing is bit faster than slice and then uint - stream.push(new Uint8Array(ab.slice((ab as any).byteOffset, ab.byteLength))); // eslint-disable-line @typescript-eslint/no-explicit-any - if (isLast) { - stream.push(null); - } - }); + this.onData((ab: ArrayBuffer, isLast: boolean) => { + // uint and then slicing is bit faster than slice and then uint + stream.push(new Uint8Array(ab.slice((ab as any).byteOffset, ab.byteLength))); // eslint-disable-line @typescript-eslint/no-explicit-any + if (isLast) { + stream.push(null); + } + }); - return stream; - }; + return stream; + }; - res.body = () => stob(res.bodyStream()); + res.body = () => stob(res.bodyStream()); - if (contType.includes('application/json')) - res.json = async () => JSON.parse(await res.body()); - if (contTypes.map(t => contType.includes(t)).includes(true)) - res.formData = formData.bind(res, contType); + if (contType.includes("application/json")) res.json = async () => JSON.parse(await res.body()); + if (contTypes.map((t) => contType.includes(t)).includes(true)) res.formData = formData.bind(res, contType); }; class BaseApp { - _sockets = new Map(); - ws!: TemplatedApp['ws']; - get!: TemplatedApp['get']; - _post!: TemplatedApp['post']; - _put!: TemplatedApp['put']; - _patch!: TemplatedApp['patch']; - _listen!: TemplatedApp['listen']; + _sockets = new Map(); + ws!: TemplatedApp["ws"]; + get!: TemplatedApp["get"]; + _post!: TemplatedApp["post"]; + _put!: TemplatedApp["put"]; + _patch!: TemplatedApp["patch"]; + _listen!: TemplatedApp["listen"]; - post(pattern: string, handler: Handler) { - if (typeof handler !== 'function') - throw Error(`handler should be a function, given ${typeof handler}.`); - this._post(pattern, (res, req) => { - handleBody(res, req); - handler(res, req); - }); - return this; - } + post(pattern: string, handler: Handler) { + if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`); + this._post(pattern, (res, req) => { + handleBody(res, req); + handler(res, req); + }); + return this; + } - put(pattern: string, handler: Handler) { - if (typeof handler !== 'function') - throw Error(`handler should be a function, given ${typeof handler}.`); - this._put(pattern, (res, req) => { - handleBody(res, req); + put(pattern: string, handler: Handler) { + if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`); + this._put(pattern, (res, req) => { + handleBody(res, req); - handler(res, req); - }); - return this; - } + handler(res, req); + }); + return this; + } - patch(pattern: string, handler: Handler) { - if (typeof handler !== 'function') - throw Error(`handler should be a function, given ${typeof handler}.`); - this._patch(pattern, (res, req) => { - handleBody(res, req); + patch(pattern: string, handler: Handler) { + if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`); + this._patch(pattern, (res, req) => { + handleBody(res, req); - handler(res, req); - }); - return this; - } + handler(res, req); + }); + return this; + } - listen(h: string | number, p: Function | number = noOp, cb?: Function) { - if (typeof p === 'number' && typeof h === 'string') { - this._listen(h, p, socket => { - this._sockets.set(p, socket); - if (cb === undefined) { - throw new Error('cb undefined'); + listen(h: string | number, p: Function | number = noOp, cb?: Function) { + if (typeof p === "number" && typeof h === "string") { + this._listen(h, p, (socket) => { + this._sockets.set(p, socket); + if (cb === undefined) { + throw new Error("cb undefined"); + } + cb(socket); + }); + } else if (typeof h === "number" && typeof p === "function") { + this._listen(h, (socket) => { + this._sockets.set(h, socket); + p(socket); + }); + } else { + throw Error("Argument types: (host: string, port: number, cb?: Function) | (port: number, cb?: Function)"); } - cb(socket); - }); - } else if (typeof h === 'number' && typeof p === 'function') { - this._listen(h, socket => { - this._sockets.set(h, socket); - p(socket); - }); - } else { - throw Error( - 'Argument types: (host: string, port: number, cb?: Function) | (port: number, cb?: Function)' - ); + + return this; } - return this; - } - - close(port: null | number = null) { - if (port) { - this._sockets.has(port) && us_listen_socket_close(this._sockets.get(port)); - this._sockets.delete(port); - } else { - this._sockets.forEach(app => { - us_listen_socket_close(app); - }); - this._sockets.clear(); + close(port: null | number = null) { + if (port) { + this._sockets.has(port) && us_listen_socket_close(this._sockets.get(port)); + this._sockets.delete(port); + } else { + this._sockets.forEach((app) => { + us_listen_socket_close(app); + }); + this._sockets.clear(); + } + return this; } - return this; - } } export default BaseApp; diff --git a/back/src/Server/server/formdata.ts b/back/src/Server/server/formdata.ts index 9dd08440..66e51db4 100644 --- a/back/src/Server/server/formdata.ts +++ b/back/src/Server/server/formdata.ts @@ -1,100 +1,99 @@ -import { createWriteStream } from 'fs'; -import { join, dirname } from 'path'; -import Busboy from 'busboy'; -import mkdirp from 'mkdirp'; +import { createWriteStream } from "fs"; +import { join, dirname } from "path"; +import Busboy from "busboy"; +import mkdirp from "mkdirp"; function formData( - contType: string, - options: busboy.BusboyConfig & { - abortOnLimit?: boolean; - tmpDir?: string; - onFile?: ( - fieldname: string, - file: NodeJS.ReadableStream, - filename: string, - encoding: string, - mimetype: string - ) => string; - onField?: (fieldname: string, value: any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any - filename?: (oldName: string) => string; - } = {} + contType: string, + options: busboy.BusboyConfig & { + abortOnLimit?: boolean; + tmpDir?: string; + onFile?: ( + fieldname: string, + file: NodeJS.ReadableStream, + filename: string, + encoding: string, + mimetype: string + ) => string; + onField?: (fieldname: string, value: any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any + filename?: (oldName: string) => string; + } = {} ) { - console.log('Enter form data'); - options.headers = { - 'content-type': contType - }; + console.log("Enter form data"); + options.headers = { + "content-type": contType, + }; - return new Promise((resolve, reject) => { - const busb = new Busboy(options); - const ret = {}; + return new Promise((resolve, reject) => { + const busb = new Busboy(options); + const ret = {}; - this.bodyStream().pipe(busb); + this.bodyStream().pipe(busb); - busb.on('limit', () => { - if (options.abortOnLimit) { - reject(Error('limit')); - } + busb.on("limit", () => { + if (options.abortOnLimit) { + reject(Error("limit")); + } + }); + + busb.on("file", function (fieldname, file, filename, encoding, mimetype) { + const value: { filePath: string | undefined; filename: string; encoding: string; mimetype: string } = { + filename, + encoding, + mimetype, + filePath: undefined, + }; + + if (typeof options.tmpDir === "string") { + if (typeof options.filename === "function") filename = options.filename(filename); + const fileToSave = join(options.tmpDir, filename); + mkdirp(dirname(fileToSave)); + + file.pipe(createWriteStream(fileToSave)); + value.filePath = fileToSave; + } + if (typeof options.onFile === "function") { + value.filePath = options.onFile(fieldname, file, filename, encoding, mimetype) || value.filePath; + } + + setRetValue(ret, fieldname, value); + }); + + busb.on("field", function (fieldname, value) { + if (typeof options.onField === "function") options.onField(fieldname, value); + + setRetValue(ret, fieldname, value); + }); + + busb.on("finish", function () { + resolve(ret); + }); + + busb.on("error", reject); }); - - busb.on('file', function(fieldname, file, filename, encoding, mimetype) { - const value: { filePath: string|undefined, filename: string, encoding:string, mimetype: string } = { - filename, - encoding, - mimetype, - filePath: undefined - }; - - if (typeof options.tmpDir === 'string') { - if (typeof options.filename === 'function') filename = options.filename(filename); - const fileToSave = join(options.tmpDir, filename); - mkdirp(dirname(fileToSave)); - - file.pipe(createWriteStream(fileToSave)); - value.filePath = fileToSave; - } - if (typeof options.onFile === 'function') { - value.filePath = - options.onFile(fieldname, file, filename, encoding, mimetype) || value.filePath; - } - - setRetValue(ret, fieldname, value); - }); - - busb.on('field', function(fieldname, value) { - if (typeof options.onField === 'function') options.onField(fieldname, value); - - setRetValue(ret, fieldname, value); - }); - - busb.on('finish', function() { - resolve(ret); - }); - - busb.on('error', reject); - }); } function setRetValue( - ret: { [x: string]: any }, // eslint-disable-line @typescript-eslint/no-explicit-any - fieldname: string, - value: { filename: string; encoding: string; mimetype: string; filePath?: string } | any // eslint-disable-line @typescript-eslint/no-explicit-any + ret: { [x: string]: any }, // eslint-disable-line @typescript-eslint/no-explicit-any + fieldname: string, + value: { filename: string; encoding: string; mimetype: string; filePath?: string } | any // eslint-disable-line @typescript-eslint/no-explicit-any ) { - if (fieldname.endsWith('[]')) { - fieldname = fieldname.slice(0, fieldname.length - 2); - if (Array.isArray(ret[fieldname])) { - ret[fieldname].push(value); + if (fieldname.endsWith("[]")) { + fieldname = fieldname.slice(0, fieldname.length - 2); + if (Array.isArray(ret[fieldname])) { + ret[fieldname].push(value); + } else { + ret[fieldname] = [value]; + } } else { - ret[fieldname] = [value]; + if (Array.isArray(ret[fieldname])) { + ret[fieldname].push(value); + } else if (ret[fieldname]) { + ret[fieldname] = [ret[fieldname], value]; + } else { + ret[fieldname] = value; + } } - } else { - if (Array.isArray(ret[fieldname])) { - ret[fieldname].push(value); - } else if (ret[fieldname]) { - ret[fieldname] = [ret[fieldname], value]; - } else { - ret[fieldname] = value; - } - } } export default formData; diff --git a/back/src/Server/server/sslapp.ts b/back/src/Server/server/sslapp.ts index 46ae89a5..80df0e4a 100644 --- a/back/src/Server/server/sslapp.ts +++ b/back/src/Server/server/sslapp.ts @@ -1,13 +1,13 @@ -import { SSLApp as _SSLApp, AppOptions } from 'uWebSockets.js'; -import BaseApp from './baseapp'; -import { extend } from './utils'; -import { UwsApp } from './types'; +import { SSLApp as _SSLApp, AppOptions } from "uWebSockets.js"; +import BaseApp from "./baseapp"; +import { extend } from "./utils"; +import { UwsApp } from "./types"; class SSLApp extends (_SSLApp) { - constructor(options: AppOptions) { - super(options); // eslint-disable-line constructor-super - extend(this, new BaseApp()); - } + constructor(options: AppOptions) { + super(options); // eslint-disable-line constructor-super + extend(this, new BaseApp()); + } } export default SSLApp; diff --git a/back/src/Server/server/types.ts b/back/src/Server/server/types.ts index 3d0f48c7..afc21d17 100644 --- a/back/src/Server/server/types.ts +++ b/back/src/Server/server/types.ts @@ -1,9 +1,9 @@ -import { AppOptions, TemplatedApp, HttpResponse, HttpRequest } from 'uWebSockets.js'; +import { AppOptions, TemplatedApp, HttpResponse, HttpRequest } from "uWebSockets.js"; export type UwsApp = { - (options: AppOptions): TemplatedApp; - new (options: AppOptions): TemplatedApp; - prototype: TemplatedApp; + (options: AppOptions): TemplatedApp; + new (options: AppOptions): TemplatedApp; + prototype: TemplatedApp; }; export type Handler = (res: HttpResponse, req: HttpRequest) => void; diff --git a/back/src/Server/server/utils.ts b/back/src/Server/server/utils.ts index 80ea3938..9816de54 100644 --- a/back/src/Server/server/utils.ts +++ b/back/src/Server/server/utils.ts @@ -1,37 +1,36 @@ -import { ReadStream } from 'fs'; +import { ReadStream } from "fs"; -function extend(who: any, from: any, overwrite = true) { // eslint-disable-line @typescript-eslint/no-explicit-any - const ownProps = Object.getOwnPropertyNames(Object.getPrototypeOf(from)).concat( - Object.keys(from) - ); - ownProps.forEach(prop => { - if (prop === 'constructor' || from[prop] === undefined) return; - if (who[prop] && overwrite) { - who[`_${prop}`] = who[prop]; - } - if (typeof from[prop] === 'function') who[prop] = from[prop].bind(who); - else who[prop] = from[prop]; - }); +function extend(who: any, from: any, overwrite = true) { + // eslint-disable-line @typescript-eslint/no-explicit-any + const ownProps = Object.getOwnPropertyNames(Object.getPrototypeOf(from)).concat(Object.keys(from)); + ownProps.forEach((prop) => { + if (prop === "constructor" || from[prop] === undefined) return; + if (who[prop] && overwrite) { + who[`_${prop}`] = who[prop]; + } + if (typeof from[prop] === "function") who[prop] = from[prop].bind(who); + else who[prop] = from[prop]; + }); } function stob(stream: ReadStream): Promise { - return new Promise(resolve => { - const buffers: Buffer[] = []; - stream.on('data', buffers.push.bind(buffers)); + return new Promise((resolve) => { + const buffers: Buffer[] = []; + stream.on("data", buffers.push.bind(buffers)); - stream.on('end', () => { - switch (buffers.length) { - case 0: - resolve(Buffer.allocUnsafe(0)); - break; - case 1: - resolve(buffers[0]); - break; - default: - resolve(Buffer.concat(buffers)); - } + stream.on("end", () => { + switch (buffers.length) { + case 0: + resolve(Buffer.allocUnsafe(0)); + break; + case 1: + resolve(buffers[0]); + break; + default: + resolve(Buffer.concat(buffers)); + } + }); }); - }); } export { extend, stob }; diff --git a/back/src/Server/sifrr.server.ts b/back/src/Server/sifrr.server.ts index 47fba02c..4ef03721 100644 --- a/back/src/Server/sifrr.server.ts +++ b/back/src/Server/sifrr.server.ts @@ -1,19 +1,19 @@ -import { parse } from 'query-string'; -import { HttpRequest } from 'uWebSockets.js'; -import App from './server/app'; -import SSLApp from './server/sslapp'; -import * as types from './server/types'; +import { parse } from "query-string"; +import { HttpRequest } from "uWebSockets.js"; +import App from "./server/app"; +import SSLApp from "./server/sslapp"; +import * as types from "./server/types"; const getQuery = (req: HttpRequest) => { - return parse(req.getQuery()); + return parse(req.getQuery()); }; export { App, SSLApp, getQuery }; -export * from './server/types'; +export * from "./server/types"; export default { - App, - SSLApp, - getQuery, - ...types + App, + SSLApp, + getQuery, + ...types, }; diff --git a/back/src/Services/ArrayHelper.ts b/back/src/Services/ArrayHelper.ts index 67321d1b..8af1da9f 100644 --- a/back/src/Services/ArrayHelper.ts +++ b/back/src/Services/ArrayHelper.ts @@ -1,3 +1,3 @@ -export const arrayIntersect = (array1: string[], array2: string[]) : boolean => { - return array1.filter(value => array2.includes(value)).length > 0; -} \ No newline at end of file +export const arrayIntersect = (array1: string[], array2: string[]): boolean => { + return array1.filter((value) => array2.includes(value)).length > 0; +}; diff --git a/back/src/Services/ClientEventsEmitter.ts b/back/src/Services/ClientEventsEmitter.ts index 381137a1..0f56d55c 100644 --- a/back/src/Services/ClientEventsEmitter.ts +++ b/back/src/Services/ClientEventsEmitter.ts @@ -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 { diff --git a/back/src/Services/CpuTracker.ts b/back/src/Services/CpuTracker.ts index c7d57f3d..3d06ca70 100644 --- a/back/src/Services/CpuTracker.ts +++ b/back/src/Services/CpuTracker.ts @@ -1,6 +1,6 @@ -import {CPU_OVERHEAT_THRESHOLD} from "../Enum/EnvironmentVariable"; +import { CPU_OVERHEAT_THRESHOLD } from "../Enum/EnvironmentVariable"; -function secNSec2ms(secNSec: Array|number) { +function secNSec2ms(secNSec: Array | 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; diff --git a/back/src/Services/GaugeManager.ts b/back/src/Services/GaugeManager.ts index 80712856..6d2183d8 100644 --- a/back/src/Services/GaugeManager.ts +++ b/back/src/Services/GaugeManager.ts @@ -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 { @@ -10,29 +10,29 @@ class GaugeManager { constructor() { this.nbRoomsGauge = new Gauge({ - name: 'workadventure_nb_rooms', - help: 'Number of active rooms' + name: "workadventure_nb_rooms", + help: "Number of active rooms", }); 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"], }); } @@ -54,13 +54,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(); \ No newline at end of file +export const gaugeManager = new GaugeManager(); diff --git a/back/src/Services/MessageHelpers.ts b/back/src/Services/MessageHelpers.ts index b2600a4a..493f7173 100644 --- a/back/src/Services/MessageHelpers.ts +++ b/back/src/Services/MessageHelpers.ts @@ -1,5 +1,5 @@ -import {ErrorMessage, ServerToClientMessage} from "../Messages/generated/messages_pb"; -import {UserSocket} from "_Model/User"; +import { ErrorMessage, ServerToClientMessage } from "../Messages/generated/messages_pb"; +import { UserSocket } from "_Model/User"; export function emitError(Client: UserSocket, message: string): void { const errorMessage = new ErrorMessage(); @@ -9,7 +9,7 @@ export function emitError(Client: UserSocket, message: string): void { serverToClientMessage.setErrormessage(errorMessage); //if (!Client.disconnecting) { - Client.write(serverToClientMessage); + Client.write(serverToClientMessage); //} console.warn(message); } diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index a56a1ac4..e61763cd 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -1,4 +1,4 @@ -import {GameRoom} from "../Model/GameRoom"; +import { GameRoom } from "../Model/GameRoom"; import { ItemEventMessage, ItemStateMessage, @@ -27,39 +27,39 @@ import { WorldFullWarningMessage, UserLeftZoneMessage, EmoteEventMessage, - BanUserMessage, RefreshRoomMessage, EmotePromptMessage, + BanUserMessage, + RefreshRoomMessage, + EmotePromptMessage, } from "../Messages/generated/messages_pb"; -import {User, UserSocket} from "../Model/User"; -import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils"; -import {Group} from "../Model/Group"; -import {cpuTracker} from "./CpuTracker"; +import { User, UserSocket } from "../Model/User"; +import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils"; +import { Group } from "../Model/Group"; +import { cpuTracker } from "./CpuTracker"; import { GROUP_RADIUS, JITSI_ISS, MINIMUM_DISTANCE, SECRET_JITSI_KEY, - TURN_STATIC_AUTH_SECRET + TURN_STATIC_AUTH_SECRET, } from "../Enum/EnvironmentVariable"; -import {Movable} from "../Model/Movable"; -import {PositionInterface} from "../Model/PositionInterface"; +import { Movable } from "../Model/Movable"; +import { PositionInterface } from "../Model/PositionInterface"; import Jwt from "jsonwebtoken"; -import {JITSI_URL} from "../Enum/EnvironmentVariable"; -import {clientEventsEmitter} from "./ClientEventsEmitter"; -import {gaugeManager} from "./GaugeManager"; -import {ZoneSocket} from "../RoomManager"; -import {Zone} from "_Model/Zone"; +import { JITSI_URL } from "../Enum/EnvironmentVariable"; +import { clientEventsEmitter } from "./ClientEventsEmitter"; +import { gaugeManager } from "./GaugeManager"; +import { ZoneSocket } from "../RoomManager"; +import { Zone } from "_Model/Zone"; import Debug from "debug"; -import {Admin} from "_Model/Admin"; +import { Admin } from "_Model/Admin"; import crypto from "crypto"; - -const debug = Debug('sockermanager'); +const debug = Debug("sockermanager"); function emitZoneMessage(subMessage: SubToPusherMessage, socket: ZoneSocket): void { // TODO: should we batch those every 100ms? const batchMessage = new BatchToPusherMessage(); batchMessage.addPayload(subMessage); - socket.write(batchMessage); } @@ -68,7 +68,6 @@ export class SocketManager { private rooms: Map = new Map(); constructor() { - clientEventsEmitter.registerToClientJoin((clientUUid: string, roomId: string) => { gaugeManager.incNbClientPerRoomGauge(roomId); }); @@ -77,16 +76,18 @@ export class SocketManager { }); } - public async handleJoinRoom(socket: UserSocket, joinRoomMessage: JoinRoomMessage): Promise<{ room: GameRoom; user: User }> { - + public async handleJoinRoom( + socket: UserSocket, + joinRoomMessage: JoinRoomMessage + ): Promise<{ room: GameRoom; user: User }> { //join new previous room - const {room, user} = await this.joinRoom(socket, joinRoomMessage); - + const { room, user } = await this.joinRoom(socket, joinRoomMessage); + if (!socket.writable) { - console.warn('Socket was aborted'); + console.warn("Socket was aborted"); return { room, - user + user, }; } const roomJoinedMessage = new RoomJoinedMessage(); @@ -108,9 +109,8 @@ export class SocketManager { return { room, - user + user, }; - } handleUserMovesMessage(room: GameRoom, user: User, userMovesMessage: UserMovesMessage) { @@ -124,13 +124,12 @@ export class SocketManager { } if (position === undefined) { - throw new Error('Position not found in message'); + throw new Error("Position not found in message"); } const viewport = userMoves.viewport; if (viewport === undefined) { - throw new Error('Viewport not found in message'); + throw new Error("Viewport not found in message"); } - // update position in the world room.updatePosition(user, ProtobufUtils.toPointInterface(position)); @@ -189,7 +188,11 @@ export class SocketManager { //send only at user const remoteUser = room.getUsers().get(data.getReceiverid()); if (remoteUser === undefined) { - console.warn("While exchanging a WebRTC signal: client with id ", data.getReceiverid(), " does not exist. This might be a race condition."); + console.warn( + "While exchanging a WebRTC signal: client with id ", + data.getReceiverid(), + " does not exist. This might be a race condition." + ); return; } @@ -197,8 +200,8 @@ export class SocketManager { webrtcSignalToClient.setUserid(user.id); webrtcSignalToClient.setSignal(data.getSignal()); // TODO: only compute credentials if data.signal.type === "offer" - if (TURN_STATIC_AUTH_SECRET !== '') { - const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET); + if (TURN_STATIC_AUTH_SECRET !== "") { + const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET); webrtcSignalToClient.setWebrtcusername(username); webrtcSignalToClient.setWebrtcpassword(password); } @@ -207,7 +210,7 @@ export class SocketManager { serverToClientMessage.setWebrtcsignaltoclientmessage(webrtcSignalToClient); //if (!client.disconnecting) { - remoteUser.socket.write(serverToClientMessage); + remoteUser.socket.write(serverToClientMessage); //} } @@ -215,7 +218,11 @@ export class SocketManager { //send only at user const remoteUser = room.getUsers().get(data.getReceiverid()); if (remoteUser === undefined) { - console.warn("While exchanging a WEBRTC_SCREEN_SHARING signal: client with id ", data.getReceiverid(), " does not exist. This might be a race condition."); + console.warn( + "While exchanging a WEBRTC_SCREEN_SHARING signal: client with id ", + data.getReceiverid(), + " does not exist. This might be a race condition." + ); return; } @@ -223,8 +230,8 @@ export class SocketManager { webrtcSignalToClient.setUserid(user.id); webrtcSignalToClient.setSignal(data.getSignal()); // TODO: only compute credentials if data.signal.type === "offer" - if (TURN_STATIC_AUTH_SECRET !== '') { - const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET); + if (TURN_STATIC_AUTH_SECRET !== "") { + const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET); webrtcSignalToClient.setWebrtcusername(username); webrtcSignalToClient.setWebrtcpassword(password); } @@ -233,11 +240,11 @@ export class SocketManager { serverToClientMessage.setWebrtcscreensharingsignaltoclientmessage(webrtcSignalToClient); //if (!client.disconnecting) { - remoteUser.socket.write(serverToClientMessage); + remoteUser.socket.write(serverToClientMessage); //} } - leaveRoom(room: GameRoom, user: User){ + leaveRoom(room: GameRoom, user: User) { // leave previous room and world try { //user leave previous world @@ -249,33 +256,39 @@ export class SocketManager { } } finally { clientEventsEmitter.emitClientLeave(user.uuid, room.roomId); - console.log('A user left'); + console.log("A user left"); } } async getOrCreateRoom(roomId: string): Promise { //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 GameRoom( roomId, (user: User, group: Group) => this.joinWebRtcRoom(user, group), (user: User, group: Group) => this.disConnectedUser(user, group), MINIMUM_DISTANCE, GROUP_RADIUS, - (thing: Movable, fromZone: Zone|null, listener: ZoneSocket) => this.onZoneEnter(thing, fromZone, listener), - (thing: Movable, position:PositionInterface, listener: ZoneSocket) => this.onClientMove(thing, position, listener), - (thing: Movable, newZone: Zone|null, listener: ZoneSocket) => this.onClientLeave(thing, newZone, listener), - (emoteEventMessage:EmoteEventMessage, listener: ZoneSocket) => this.onEmote(emoteEventMessage, listener), + (thing: Movable, fromZone: Zone | null, listener: ZoneSocket) => + this.onZoneEnter(thing, fromZone, listener), + (thing: Movable, position: PositionInterface, listener: ZoneSocket) => + this.onClientMove(thing, position, listener), + (thing: Movable, newZone: Zone | null, listener: ZoneSocket) => + this.onClientLeave(thing, newZone, listener), + (emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) => + this.onEmote(emoteEventMessage, listener) ); gaugeManager.incNbRoomGauge(); this.rooms.set(roomId, world); } - return Promise.resolve(world) + return Promise.resolve(world); } - private async joinRoom(socket: UserSocket, joinRoomMessage: JoinRoomMessage): Promise<{ room: GameRoom; user: User }> { - + private async joinRoom( + socket: UserSocket, + joinRoomMessage: JoinRoomMessage + ): Promise<{ room: GameRoom; user: User }> { const roomId = joinRoomMessage.getRoomid(); const room = await socketManager.getOrCreateRoom(roomId); @@ -284,15 +297,15 @@ export class SocketManager { const user = room.join(socket, joinRoomMessage); clientEventsEmitter.emitClientJoin(user.uuid, roomId); - console.log(new Date().toISOString() + ' A user joined'); - return {room, user}; + console.log(new Date().toISOString() + " A user joined"); + return { room, user }; } - private onZoneEnter(thing: Movable, fromZone: Zone|null, listener: ZoneSocket) { + private onZoneEnter(thing: Movable, fromZone: Zone | null, listener: ZoneSocket) { if (thing instanceof User) { const userJoinedZoneMessage = new UserJoinedZoneMessage(); if (!Number.isInteger(thing.id)) { - throw new Error('clientUser.userId is not an integer '+thing.id); + throw new Error("clientUser.userId is not an integer " + thing.id); } userJoinedZoneMessage.setUserid(thing.id); userJoinedZoneMessage.setName(thing.name); @@ -312,11 +325,11 @@ export class SocketManager { } else if (thing instanceof Group) { this.emitCreateUpdateGroupEvent(listener, fromZone, thing); } else { - console.error('Unexpected type for Movable.'); + console.error("Unexpected type for Movable."); } } - private onClientMove(thing: Movable, position:PositionInterface, listener: ZoneSocket): void { + private onClientMove(thing: Movable, position: PositionInterface, listener: ZoneSocket): void { if (thing instanceof User) { const userMovedMessage = new UserMovedMessage(); userMovedMessage.setUserid(thing.id); @@ -331,21 +344,20 @@ export class SocketManager { } else if (thing instanceof Group) { this.emitCreateUpdateGroupEvent(listener, null, thing); } else { - console.error('Unexpected type for Movable.'); + console.error("Unexpected type for Movable."); } } - private onClientLeave(thing: Movable, newZone: Zone|null, listener: ZoneSocket) { + private onClientLeave(thing: Movable, newZone: Zone | null, listener: ZoneSocket) { if (thing instanceof User) { this.emitUserLeftEvent(listener, thing.id, newZone); } else if (thing instanceof Group) { this.emitDeleteGroupEvent(listener, thing.getId(), newZone); } else { - console.error('Unexpected type for Movable.'); + console.error("Unexpected type for Movable."); } } - private onEmote(emoteEventMessage: EmoteEventMessage, client: ZoneSocket) { const subMessage = new SubToPusherMessage(); subMessage.setEmoteeventmessage(emoteEventMessage); @@ -353,7 +365,7 @@ export class SocketManager { emitZoneMessage(subMessage, client); } - private emitCreateUpdateGroupEvent(client: ZoneSocket, fromZone: Zone|null, group: Group): void { + private emitCreateUpdateGroupEvent(client: ZoneSocket, fromZone: Zone | null, group: Group): void { const position = group.getPosition(); const pointMessage = new PointMessage(); pointMessage.setX(Math.floor(position.x)); @@ -371,7 +383,7 @@ export class SocketManager { //client.emitInBatch(subMessage); } - private emitDeleteGroupEvent(client: ZoneSocket, groupId: number, newZone: Zone|null): void { + private emitDeleteGroupEvent(client: ZoneSocket, groupId: number, newZone: Zone | null): void { const groupDeleteMessage = new GroupLeftZoneMessage(); groupDeleteMessage.setGroupid(groupId); groupDeleteMessage.setTozone(this.toProtoZone(newZone)); @@ -383,7 +395,7 @@ export class SocketManager { //user.emitInBatch(subMessage); } - private emitUserLeftEvent(client: ZoneSocket, userId: number, newZone: Zone|null): void { + private emitUserLeftEvent(client: ZoneSocket, userId: number, newZone: Zone | null): void { const userLeftMessage = new UserLeftZoneMessage(); userLeftMessage.setUserid(userId); userLeftMessage.setTozone(this.toProtoZone(newZone)); @@ -394,7 +406,7 @@ export class SocketManager { emitZoneMessage(subMessage, client); } - private toProtoZone(zone: Zone|null): ProtoZone|undefined { + private toProtoZone(zone: Zone | null): ProtoZone | undefined { if (zone !== null) { const zoneMessage = new ProtoZone(); zoneMessage.setX(zone.x); @@ -405,7 +417,6 @@ export class SocketManager { } private joinWebRtcRoom(user: User, group: Group) { - for (const otherUser of group.getUsers()) { if (user === otherUser) { continue; @@ -416,8 +427,8 @@ export class SocketManager { webrtcStartMessage1.setUserid(otherUser.id); webrtcStartMessage1.setName(otherUser.name); webrtcStartMessage1.setInitiator(true); - if (TURN_STATIC_AUTH_SECRET !== '') { - const {username, password} = this.getTURNCredentials(''+otherUser.id, TURN_STATIC_AUTH_SECRET); + if (TURN_STATIC_AUTH_SECRET !== "") { + const { username, password } = this.getTURNCredentials("" + otherUser.id, TURN_STATIC_AUTH_SECRET); webrtcStartMessage1.setWebrtcusername(username); webrtcStartMessage1.setWebrtcpassword(password); } @@ -426,16 +437,16 @@ export class SocketManager { serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1); //if (!user.socket.disconnecting) { - user.socket.write(serverToClientMessage1); - //console.log('Sending webrtcstart initiator to '+user.socket.userId) + user.socket.write(serverToClientMessage1); + //console.log('Sending webrtcstart initiator to '+user.socket.userId) //} const webrtcStartMessage2 = new WebRtcStartMessage(); webrtcStartMessage2.setUserid(user.id); webrtcStartMessage2.setName(user.name); webrtcStartMessage2.setInitiator(false); - if (TURN_STATIC_AUTH_SECRET !== '') { - const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET); + if (TURN_STATIC_AUTH_SECRET !== "") { + const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET); webrtcStartMessage2.setWebrtcusername(username); webrtcStartMessage2.setWebrtcpassword(password); } @@ -444,10 +455,9 @@ export class SocketManager { serverToClientMessage2.setWebrtcstartmessage(webrtcStartMessage2); //if (!otherUser.socket.disconnecting) { - otherUser.socket.write(serverToClientMessage2); - //console.log('Sending webrtcstart to '+otherUser.socket.userId) + otherUser.socket.write(serverToClientMessage2); + //console.log('Sending webrtcstart to '+otherUser.socket.userId) //} - } } @@ -456,17 +466,17 @@ export class SocketManager { * and the Coturn server. * The Coturn server should be initialized with parameters: `--use-auth-secret --static-auth-secret=MySecretKey` */ - private getTURNCredentials(name: string, secret: string): {username: string, password: string} { - const unixTimeStamp = Math.floor(Date.now()/1000) + 4*3600; // this credential would be valid for the next 4 hours - const username = [unixTimeStamp, name].join(':'); - const hmac = crypto.createHmac('sha1', secret); - hmac.setEncoding('base64'); + private getTURNCredentials(name: string, secret: string): { username: string; password: string } { + const unixTimeStamp = Math.floor(Date.now() / 1000) + 4 * 3600; // this credential would be valid for the next 4 hours + const username = [unixTimeStamp, name].join(":"); + const hmac = crypto.createHmac("sha1", secret); + hmac.setEncoding("base64"); hmac.write(username); hmac.end(); const password = hmac.read(); return { username: username, - password: password + password: password, }; } @@ -489,10 +499,9 @@ export class SocketManager { serverToClientMessage1.setWebrtcdisconnectmessage(webrtcDisconnectMessage1); //if (!otherUser.socket.disconnecting) { - otherUser.socket.write(serverToClientMessage1); + otherUser.socket.write(serverToClientMessage1); //} - const webrtcDisconnectMessage2 = new WebRtcDisconnectMessage(); webrtcDisconnectMessage2.setUserid(otherUser.id); @@ -500,7 +509,7 @@ export class SocketManager { serverToClientMessage2.setWebrtcdisconnectmessage(webrtcDisconnectMessage2); //if (!user.socket.disconnecting) { - user.socket.write(serverToClientMessage2); + user.socket.write(serverToClientMessage2); //} } } @@ -517,40 +526,41 @@ export class SocketManager { console.error('An error occurred on "emitPlayGlobalMessage" event'); console.error(e); } - } public getWorlds(): Map { return this.rooms; } - public handleQueryJitsiJwtMessage(user: User, queryJitsiJwtMessage: QueryJitsiJwtMessage) { 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 = user.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); @@ -562,7 +572,7 @@ export class SocketManager { user.socket.write(serverToClientMessage); } - public handlerSendUserMessage(user: User, sendUserMessageToSend: SendUserMessage){ + public handlerSendUserMessage(user: User, sendUserMessageToSend: SendUserMessage) { const sendUserMessage = new SendUserMessage(); sendUserMessage.setMessage(sendUserMessageToSend.getMessage()); sendUserMessage.setType(sendUserMessageToSend.getType()); @@ -572,7 +582,7 @@ export class SocketManager { user.socket.write(serverToClientMessage); } - public handlerBanUserMessage(room: GameRoom, user: User, banUserMessageToSend: BanUserMessage){ + public handlerBanUserMessage(room: GameRoom, user: User, banUserMessageToSend: BanUserMessage) { const banUserMessage = new BanUserMessage(); banUserMessage.setMessage(banUserMessageToSend.getMessage()); banUserMessage.setType(banUserMessageToSend.getType()); @@ -592,7 +602,7 @@ export class SocketManager { public addZoneListener(call: ZoneSocket, roomId: string, x: number, y: number): void { const room = this.rooms.get(roomId); if (!room) { - console.error("In addZoneListener, could not find room with id '" + roomId + "'"); + console.error("In addZoneListener, could not find room with id '" + roomId + "'"); return; } @@ -636,7 +646,7 @@ export class SocketManager { removeZoneListener(call: ZoneSocket, roomId: string, x: number, y: number) { const room = this.rooms.get(roomId); if (!room) { - console.error("In removeZoneListener, could not find room with id '" + roomId + "'"); + console.error("In removeZoneListener, could not find room with id '" + roomId + "'"); return; } @@ -651,7 +661,7 @@ export class SocketManager { return room; } - public leaveAdminRoom(room: GameRoom, admin: Admin){ + public leaveAdminRoom(room: GameRoom, admin: Admin) { room.adminLeave(admin); if (room.isEmpty()) { this.rooms.delete(room.roomId); @@ -663,19 +673,27 @@ export class SocketManager { public sendAdminMessage(roomId: string, recipientUuid: string, message: string): void { const room = this.rooms.get(roomId); if (!room) { - console.error("In sendAdminMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?"); + console.error( + "In sendAdminMessage, could not find room with id '" + + roomId + + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?" + ); return; } const recipient = room.getUserByUuid(recipientUuid); if (recipient === undefined) { - console.error("In sendAdminMessage, could not find user with id '" + recipientUuid + "'. Maybe the user left the room a few milliseconds ago and there was a race condition?"); + console.error( + "In sendAdminMessage, could not find user with id '" + + recipientUuid + + "'. Maybe the user left the room a few milliseconds ago and there was a race condition?" + ); return; } const sendUserMessage = new SendUserMessage(); sendUserMessage.setMessage(message); - sendUserMessage.setType('ban'); //todo: is the type correct? + sendUserMessage.setType("ban"); //todo: is the type correct? const serverToClientMessage = new ServerToClientMessage(); serverToClientMessage.setSendusermessage(sendUserMessage); @@ -686,13 +704,21 @@ export class SocketManager { public banUser(roomId: string, recipientUuid: string, message: string): void { const room = this.rooms.get(roomId); if (!room) { - console.error("In banUser, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?"); + console.error( + "In banUser, could not find room with id '" + + roomId + + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?" + ); return; } const recipient = room.getUserByUuid(recipientUuid); if (recipient === undefined) { - console.error("In banUser, could not find user with id '" + recipientUuid + "'. Maybe the user left the room a few milliseconds ago and there was a race condition?"); + console.error( + "In banUser, could not find user with id '" + + recipientUuid + + "'. Maybe the user left the room a few milliseconds ago and there was a race condition?" + ); return; } @@ -701,7 +727,7 @@ export class SocketManager { const banUserMessage = new BanUserMessage(); banUserMessage.setMessage(message); - banUserMessage.setType('banned'); + banUserMessage.setType("banned"); const serverToClientMessage = new ServerToClientMessage(); serverToClientMessage.setBanusermessage(banUserMessage); @@ -711,19 +737,22 @@ export class SocketManager { recipient.socket.end(); } - sendAdminRoomMessage(roomId: string, message: string) { const room = this.rooms.get(roomId); if (!room) { //todo: this should cause the http call to return a 500 - console.error("In sendAdminRoomMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?"); + console.error( + "In sendAdminRoomMessage, could not find room with id '" + + roomId + + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?" + ); return; } room.getUsers().forEach((recipient) => { const sendUserMessage = new SendUserMessage(); sendUserMessage.setMessage(message); - sendUserMessage.setType('message'); + sendUserMessage.setType("message"); const clientMessage = new ServerToClientMessage(); clientMessage.setSendusermessage(sendUserMessage); @@ -732,14 +761,18 @@ export class SocketManager { }); } - dispatchWorlFullWarning(roomId: string,): void { + dispatchWorlFullWarning(roomId: string): void { const room = this.rooms.get(roomId); if (!room) { //todo: this should cause the http call to return a 500 - console.error("In sendAdminRoomMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?"); + console.error( + "In sendAdminRoomMessage, could not find room with id '" + + roomId + + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?" + ); return; } - + room.getUsers().forEach((recipient) => { const worldFullMessage = new WorldFullWarningMessage(); @@ -750,17 +783,17 @@ export class SocketManager { }); } - dispatchRoomRefresh(roomId: string,): void { + dispatchRoomRefresh(roomId: string): void { const room = this.rooms.get(roomId); if (!room) { return; } - + const versionNumber = room.incrementVersion(); room.getUsers().forEach((recipient) => { const worldFullMessage = new RefreshRoomMessage(); - worldFullMessage.setRoomid(roomId) - worldFullMessage.setVersionnumber(versionNumber) + worldFullMessage.setRoomid(roomId); + worldFullMessage.setVersionnumber(versionNumber); const clientMessage = new ServerToClientMessage(); clientMessage.setRefreshroommessage(worldFullMessage); diff --git a/pusher/src/App.ts b/pusher/src/App.ts index 7a272404..81aed045 100644 --- a/pusher/src/App.ts +++ b/pusher/src/App.ts @@ -1,11 +1,11 @@ // lib/app.ts -import {IoSocketController} from "./Controller/IoSocketController"; //TODO fix import by "_Controller/..." -import {AuthenticateController} from "./Controller/AuthenticateController"; //TODO fix import by "_Controller/..." -import {MapController} from "./Controller/MapController"; -import {PrometheusController} from "./Controller/PrometheusController"; -import {DebugController} from "./Controller/DebugController"; -import {App as uwsApp} from "./Server/sifrr.server"; -import {AdminController} from "./Controller/AdminController"; +import { IoSocketController } from "./Controller/IoSocketController"; //TODO fix import by "_Controller/..." +import { AuthenticateController } from "./Controller/AuthenticateController"; //TODO fix import by "_Controller/..." +import { MapController } from "./Controller/MapController"; +import { PrometheusController } from "./Controller/PrometheusController"; +import { DebugController } from "./Controller/DebugController"; +import { App as uwsApp } from "./Server/sifrr.server"; +import { AdminController } from "./Controller/AdminController"; class App { public app: uwsApp; diff --git a/pusher/src/Controller/AdminController.ts b/pusher/src/Controller/AdminController.ts index 74d4e792..ec1bd067 100644 --- a/pusher/src/Controller/AdminController.ts +++ b/pusher/src/Controller/AdminController.ts @@ -1,19 +1,21 @@ -import {BaseController} from "./BaseController"; -import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js"; -import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable"; -import {apiClientRepository} from "../Services/ApiClientRepository"; -import {AdminRoomMessage, WorldFullWarningToRoomMessage, RefreshRoomPromptMessage} from "../Messages/generated/messages_pb"; +import { BaseController } from "./BaseController"; +import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js"; +import { ADMIN_API_TOKEN } from "../Enum/EnvironmentVariable"; +import { apiClientRepository } from "../Services/ApiClientRepository"; +import { + AdminRoomMessage, + WorldFullWarningToRoomMessage, + RefreshRoomPromptMessage, +} from "../Messages/generated/messages_pb"; - -export class AdminController extends BaseController{ - - constructor(private App : TemplatedApp) { +export class AdminController extends BaseController { + constructor(private App: TemplatedApp) { super(); this.App = App; this.receiveGlobalMessagePrompt(); this.receiveRoomEditionPrompt(); } - + receiveRoomEditionPrompt() { this.App.options("/room/refresh", (res: HttpResponse, req: HttpRequest) => { this.addCorsHeaders(res); @@ -23,25 +25,25 @@ export class AdminController extends BaseController{ // eslint-disable-next-line @typescript-eslint/no-misused-promises this.App.post("/room/refresh", async (res: HttpResponse, req: HttpRequest) => { res.onAborted(() => { - console.warn('/message request was aborted'); - }) + console.warn("/message request was aborted"); + }); - const token = req.getHeader('admin-token'); + const token = req.getHeader("admin-token"); const body = await res.json(); if (token !== ADMIN_API_TOKEN) { - console.error('Admin access refused for token: '+token) - res.writeStatus("401 Unauthorized").end('Incorrect token'); + console.error("Admin access refused for token: " + token); + res.writeStatus("401 Unauthorized").end("Incorrect token"); return; } try { - if (typeof body.roomId !== 'string') { - throw 'Incorrect roomId parameter' + if (typeof body.roomId !== "string") { + throw "Incorrect roomId parameter"; } const roomId: string = body.roomId; - await apiClientRepository.getClient(roomId).then((roomClient) =>{ + await apiClientRepository.getClient(roomId).then((roomClient) => { return new Promise((res, rej) => { const roomMessage = new RefreshRoomPromptMessage(); roomMessage.setRoomid(roomId); @@ -57,12 +59,10 @@ export class AdminController extends BaseController{ } res.writeStatus("200"); - res.end('ok'); - - + res.end("ok"); }); } - + receiveGlobalMessagePrompt() { this.App.options("/message", (res: HttpResponse, req: HttpRequest) => { this.addCorsHeaders(res); @@ -71,59 +71,57 @@ export class AdminController extends BaseController{ // eslint-disable-next-line @typescript-eslint/no-misused-promises this.App.post("/message", async (res: HttpResponse, req: HttpRequest) => { - res.onAborted(() => { - console.warn('/message request was aborted'); - }) + console.warn("/message request was aborted"); + }); - - const token = req.getHeader('admin-token'); + const token = req.getHeader("admin-token"); const body = await res.json(); - + if (token !== ADMIN_API_TOKEN) { - console.error('Admin access refused for token: '+token) - res.writeStatus("401 Unauthorized").end('Incorrect token'); + console.error("Admin access refused for token: " + token); + res.writeStatus("401 Unauthorized").end("Incorrect token"); return; } try { - if (typeof body.text !== 'string') { - throw 'Incorrect text parameter' + if (typeof body.text !== "string") { + throw "Incorrect text parameter"; } - if (body.type !== 'capacity' && body.type !== 'message') { - throw 'Incorrect type parameter' + if (body.type !== "capacity" && body.type !== "message") { + throw "Incorrect type parameter"; } - if (!body.targets || typeof body.targets !== 'object') { - throw 'Incorrect targets parameter' + if (!body.targets || typeof body.targets !== "object") { + throw "Incorrect targets parameter"; } const text: string = body.text; const type: string = body.type; const targets: string[] = body.targets; - await Promise.all(targets.map((roomId) => { - return apiClientRepository.getClient(roomId).then((roomClient) =>{ - return new Promise((res, rej) => { - if (type === 'message') { - const roomMessage = new AdminRoomMessage(); - roomMessage.setMessage(text); - roomMessage.setRoomid(roomId); + await Promise.all( + targets.map((roomId) => { + return apiClientRepository.getClient(roomId).then((roomClient) => { + return new Promise((res, rej) => { + if (type === "message") { + const roomMessage = new AdminRoomMessage(); + roomMessage.setMessage(text); + roomMessage.setRoomid(roomId); - roomClient.sendAdminMessageToRoom(roomMessage, (err) => { - err ? rej(err) : res(); - }); - } else if (type === 'capacity') { - const roomMessage = new WorldFullWarningToRoomMessage(); - roomMessage.setRoomid(roomId); - - roomClient.sendWorldFullWarningToRoom(roomMessage, (err) => { - err ? rej(err) : res(); - }); - } + roomClient.sendAdminMessageToRoom(roomMessage, (err) => { + err ? rej(err) : res(); + }); + } else if (type === "capacity") { + const roomMessage = new WorldFullWarningToRoomMessage(); + roomMessage.setRoomid(roomId); + roomClient.sendWorldFullWarningToRoom(roomMessage, (err) => { + err ? rej(err) : res(); + }); + } + }); }); - }); - })); - + }) + ); } catch (err) { this.errorToResponse(err, res); return; @@ -131,7 +129,7 @@ export class AdminController extends BaseController{ res.writeStatus("200"); this.addCorsHeaders(res); - res.end('ok'); + res.end("ok"); }); } } diff --git a/pusher/src/Controller/AuthenticateController.ts b/pusher/src/Controller/AuthenticateController.ts index 317848c0..3012e275 100644 --- a/pusher/src/Controller/AuthenticateController.ts +++ b/pusher/src/Controller/AuthenticateController.ts @@ -1,17 +1,16 @@ -import { v4 } from 'uuid'; -import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js"; -import {BaseController} from "./BaseController"; -import {adminApi} from "../Services/AdminApi"; -import {jwtTokenManager} from "../Services/JWTTokenManager"; -import {parse} from "query-string"; +import { v4 } from "uuid"; +import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js"; +import { BaseController } from "./BaseController"; +import { adminApi } from "../Services/AdminApi"; +import { jwtTokenManager } from "../Services/JWTTokenManager"; +import { parse } from "query-string"; export interface TokenInterface { - userUuid: string + userUuid: string; } export class AuthenticateController extends BaseController { - - constructor(private App : TemplatedApp) { + constructor(private App: TemplatedApp) { super(); this.register(); this.verify(); @@ -19,7 +18,7 @@ export class AuthenticateController extends BaseController { } //Try to login with an admin token - private register(){ + private register() { this.App.options("/register", (res: HttpResponse, req: HttpRequest) => { this.addCorsHeaders(res); @@ -29,15 +28,15 @@ export class AuthenticateController extends BaseController { this.App.post("/register", (res: HttpResponse, req: HttpRequest) => { (async () => { res.onAborted(() => { - console.warn('Login request was aborted'); - }) + console.warn("Login request was aborted"); + }); const param = await res.json(); //todo: what to do if the organizationMemberToken is already used? - const organizationMemberToken:string|null = param.organizationMemberToken; + const organizationMemberToken: string | null = param.organizationMemberToken; try { - if (typeof organizationMemberToken != 'string') throw new Error('No organization token'); + if (typeof organizationMemberToken != "string") throw new Error("No organization token"); const data = await adminApi.fetchMemberDataByToken(organizationMemberToken); const userUuid = data.userUuid; const organizationSlug = data.organizationSlug; @@ -49,28 +48,26 @@ export class AuthenticateController extends BaseController { const authToken = jwtTokenManager.createJWTToken(userUuid); res.writeStatus("200 OK"); this.addCorsHeaders(res); - res.end(JSON.stringify({ - authToken, - userUuid, - organizationSlug, - worldSlug, - roomSlug, - mapUrlStart, - organizationMemberToken, - textures - })); - + res.end( + JSON.stringify({ + authToken, + userUuid, + organizationSlug, + worldSlug, + roomSlug, + mapUrlStart, + organizationMemberToken, + textures, + }) + ); } catch (e) { this.errorToResponse(e, res); } - - })(); }); - } - private verify(){ + private verify() { this.App.options("/verify", (res: HttpResponse, req: HttpRequest) => { this.addCorsHeaders(res); @@ -82,50 +79,55 @@ export class AuthenticateController extends BaseController { const query = parse(req.getQuery()); res.onAborted(() => { - console.warn('verify request was aborted'); - }) + console.warn("verify request was aborted"); + }); try { await jwtTokenManager.getUserUuidFromToken(query.token as string); } catch (e) { res.writeStatus("400 Bad Request"); this.addCorsHeaders(res); - res.end(JSON.stringify({ - "success": false, - "message": "Invalid JWT token" - })); + res.end( + JSON.stringify({ + success: false, + message: "Invalid JWT token", + }) + ); return; } res.writeStatus("200 OK"); this.addCorsHeaders(res); - res.end(JSON.stringify({ - "success": true - })); + res.end( + JSON.stringify({ + success: true, + }) + ); })(); }); - } //permit to login on application. Return token to connect on Websocket IO. - private anonymLogin(){ + private anonymLogin() { this.App.options("/anonymLogin", (res: HttpResponse, req: HttpRequest) => { this.addCorsHeaders(res); res.end(); }); - this.App.post("/anonymLogin", (res: HttpResponse, req: HttpRequest) => { + this.App.post("/anonymLogin", (res: HttpResponse, req: HttpRequest) => { res.onAborted(() => { - console.warn('Login request was aborted'); - }) + console.warn("Login request was aborted"); + }); const userUuid = v4(); const authToken = jwtTokenManager.createJWTToken(userUuid); res.writeStatus("200 OK"); this.addCorsHeaders(res); - res.end(JSON.stringify({ - authToken, - userUuid, - })); + res.end( + JSON.stringify({ + authToken, + userUuid, + }) + ); }); } } diff --git a/pusher/src/Controller/BaseController.ts b/pusher/src/Controller/BaseController.ts index 91882138..ce378a55 100644 --- a/pusher/src/Controller/BaseController.ts +++ b/pusher/src/Controller/BaseController.ts @@ -1,11 +1,10 @@ -import {HttpResponse} from "uWebSockets.js"; - +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', '*'); + 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", "*"); } /** @@ -16,23 +15,23 @@ export class BaseController { if (e && e.message) { let url = e?.config?.url; if (url !== undefined) { - url = ' for URL: '+url; + url = " for URL: " + url; } else { - url = ''; + url = ""; } - console.error('ERROR: '+e.message+url); - } else if (typeof(e) === 'string') { + console.error("ERROR: " + e.message + url); + } else if (typeof e === "string") { console.error(e); } if (e.stack) { console.error(e.stack); } if (e.response) { - res.writeStatus(e.response.status+" "+e.response.statusText); + res.writeStatus(e.response.status + " " + e.response.statusText); this.addCorsHeaders(res); - res.end("An error occurred: "+e.response.status+" "+e.response.statusText); + res.end("An error occurred: " + e.response.status + " " + e.response.statusText); } else { - res.writeStatus("500 Internal Server Error") + res.writeStatus("500 Internal Server Error"); this.addCorsHeaders(res); res.end("An error occurred"); } diff --git a/pusher/src/Controller/DebugController.ts b/pusher/src/Controller/DebugController.ts index af2db139..0b0d188b 100644 --- a/pusher/src/Controller/DebugController.ts +++ b/pusher/src/Controller/DebugController.ts @@ -1,45 +1,46 @@ -import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable"; -import {IoSocketController} from "_Controller/IoSocketController"; -import {stringify} from "circular-json"; -import {HttpRequest, HttpResponse} from "uWebSockets.js"; -import { parse } from 'query-string'; -import {App} from "../Server/sifrr.server"; -import {socketManager} from "../Services/SocketManager"; +import { ADMIN_API_TOKEN } from "../Enum/EnvironmentVariable"; +import { IoSocketController } from "_Controller/IoSocketController"; +import { stringify } from "circular-json"; +import { HttpRequest, HttpResponse } from "uWebSockets.js"; +import { parse } from "query-string"; +import { App } from "../Server/sifrr.server"; +import { socketManager } from "../Services/SocketManager"; export class DebugController { - constructor(private App : App) { + constructor(private App: App) { this.getDump(); } - - getDump(){ + getDump() { this.App.get("/dump", (res: HttpResponse, req: HttpRequest) => { const query = parse(req.getQuery()); if (query.token !== ADMIN_API_TOKEN) { - return res.status(401).send('Invalid token sent!'); + return res.status(401).send("Invalid token sent!"); } - return res.writeStatus('200 OK').writeHeader('Content-Type', 'application/json').end(stringify( - socketManager.getWorlds(), - (key: unknown, value: unknown) => { - if(value instanceof Map) { - const obj: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any - for (const [mapKey, mapValue] of value.entries()) { - obj[mapKey] = mapValue; - } - return obj; - } else if(value instanceof Set) { + return res + .writeStatus("200 OK") + .writeHeader("Content-Type", "application/json") + .end( + stringify(socketManager.getWorlds(), (key: unknown, value: unknown) => { + if (value instanceof Map) { + const obj: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any + for (const [mapKey, mapValue] of value.entries()) { + obj[mapKey] = mapValue; + } + return obj; + } else if (value instanceof Set) { const obj: Array = []; for (const [setKey, setValue] of value.entries()) { obj.push(setValue); } return obj; - } else { - return value; - } - } - )); + } else { + return value; + } + }) + ); }); } } diff --git a/pusher/src/Controller/IoSocketController.ts b/pusher/src/Controller/IoSocketController.ts index b2079953..1af9d917 100644 --- a/pusher/src/Controller/IoSocketController.ts +++ b/pusher/src/Controller/IoSocketController.ts @@ -1,6 +1,6 @@ -import {CharacterLayer, ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.." -import {GameRoomPolicyTypes} from "../Model/PusherRoom"; -import {PointInterface} from "../Model/Websocket/PointInterface"; +import { CharacterLayer, ExSocketInterface } from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.." +import { GameRoomPolicyTypes } from "../Model/PusherRoom"; +import { PointInterface } from "../Model/Websocket/PointInterface"; import { SetPlayerDetailsMessage, SubMessage, @@ -18,17 +18,17 @@ import { CompanionMessage, EmotePromptMessage, } from "../Messages/generated/messages_pb"; -import {UserMovesMessage} from "../Messages/generated/messages_pb"; -import {TemplatedApp} from "uWebSockets.js" -import {parse} from "query-string"; -import {jwtTokenManager} from "../Services/JWTTokenManager"; -import {adminApi, CharacterTexture, FetchMemberDataByUuidResponse} from "../Services/AdminApi"; -import {SocketManager, socketManager} from "../Services/SocketManager"; -import {emitInBatch} from "../Services/IoSocketHelpers"; -import {ADMIN_API_TOKEN, ADMIN_API_URL, SOCKET_IDLE_TIMER} from "../Enum/EnvironmentVariable"; -import {Zone} from "_Model/Zone"; -import {ExAdminSocketInterface} from "_Model/Websocket/ExAdminSocketInterface"; -import {v4} from "uuid"; +import { UserMovesMessage } from "../Messages/generated/messages_pb"; +import { TemplatedApp } from "uWebSockets.js"; +import { parse } from "query-string"; +import { jwtTokenManager } from "../Services/JWTTokenManager"; +import { adminApi, CharacterTexture, FetchMemberDataByUuidResponse } from "../Services/AdminApi"; +import { SocketManager, socketManager } from "../Services/SocketManager"; +import { emitInBatch } from "../Services/IoSocketHelpers"; +import { ADMIN_API_TOKEN, ADMIN_API_URL, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable"; +import { Zone } from "_Model/Zone"; +import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface"; +import { v4 } from "uuid"; export class IoSocketController { private nextUserId: number = 1; @@ -39,32 +39,29 @@ export class IoSocketController { } adminRoomSocket() { - this.app.ws('/admin/rooms', { + 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 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'); + console.log("Admin access refused for token: " + token); + res.writeStatus("401 Unauthorized").end("Incorrect token"); return; } const roomId = query.roomId; - if (typeof roomId !== 'string') { - console.error('Received') - res.writeStatus("400 Bad Request").end('Missing room id'); + if (typeof roomId !== "string") { + console.error("Received"); + res.writeStatus("400 Bad Request").end("Missing room id"); return; } - res.upgrade( - {roomId}, - websocketKey, websocketProtocol, websocketExtensions, context, - ); + res.upgrade({ roomId }, websocketKey, websocketProtocol, websocketExtensions, context); }, open: (ws) => { - console.log('Admin socket connect for room: '+ws.roomId); + console.log("Admin socket connect for room: " + ws.roomId); ws.disconnecting = false; socketManager.handleAdminRoom(ws as ExAdminSocketInterface, ws.roomId as string); @@ -74,24 +71,34 @@ export class IoSocketController { const roomId = ws.roomId as string; //TODO refactor message type and data - const message: {event: string, message: {type: string, message: unknown, userUuid: string}} = + 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') { - const messageToEmit = (message.message as { message: string, type: string, userUuid: string }); - if(messageToEmit.type === 'banned'){ - socketManager.emitBan(messageToEmit.userUuid, messageToEmit.message, messageToEmit.type, ws.roomId as string); + if (message.event === "user-message") { + const messageToEmit = message.message as { message: string; type: string; userUuid: string }; + if (messageToEmit.type === "banned") { + socketManager.emitBan( + messageToEmit.userUuid, + messageToEmit.message, + messageToEmit.type, + ws.roomId as string + ); } - if(messageToEmit.type === 'ban') { - socketManager.emitSendUserMessage(messageToEmit.userUuid, messageToEmit.message, messageToEmit.type, ws.roomId as string); + if (messageToEmit.type === "ban") { + socketManager.emitSendUserMessage( + messageToEmit.userUuid, + messageToEmit.message, + messageToEmit.type, + ws.roomId as string + ); } } - }catch (err) { + } catch (err) { console.error(err); } }, close: (ws, code, message) => { - const Client = (ws as ExAdminSocketInterface); + const Client = ws as ExAdminSocketInterface; try { Client.disconnecting = true; socketManager.leaveAdminRoom(Client); @@ -99,12 +106,12 @@ export class IoSocketController { console.error('An error occurred on admin "disconnect"'); console.error(e); } - } - }) + }, + }); } ioConnection() { - this.app.ws('/room', { + this.app.ws("/room", { /* Options */ //compression: uWS.SHARED_COMPRESSOR, idleTimeout: SOCKET_IDLE_TIMER, @@ -114,7 +121,7 @@ export class IoSocketController { upgrade: (res, req, context) => { (async () => { /* Keep track of abortions */ - const upgradeAborted = {aborted: false}; + const upgradeAborted = { aborted: false }; res.onAborted(() => { /* We can simply signal that we were aborted */ @@ -123,15 +130,15 @@ export class IoSocketController { const url = req.getUrl(); 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 IPAddress = req.getHeader('x-forwarded-for'); + const websocketKey = req.getHeader("sec-websocket-key"); + const websocketProtocol = req.getHeader("sec-websocket-protocol"); + const websocketExtensions = req.getHeader("sec-websocket-extensions"); + const IPAddress = req.getHeader("x-forwarded-for"); const roomId = query.roomId; try { - if (typeof roomId !== 'string') { - throw new Error('Undefined room ID: '); + if (typeof roomId !== "string") { + throw new Error("Undefined room ID: "); } const token = query.token; @@ -143,62 +150,69 @@ export class IoSocketController { const right = Number(query.right); const name = query.name; - let companion: CompanionMessage|undefined = undefined; + let companion: CompanionMessage | undefined = undefined; - if (typeof query.companion === 'string') { + if (typeof query.companion === "string") { companion = new CompanionMessage(); companion.setName(query.companion); } - if (typeof name !== 'string') { - throw new Error('Expecting name'); + if (typeof name !== "string") { + throw new Error("Expecting name"); } - if (name === '') { - throw new Error('No empty name'); + if (name === "") { + throw new Error("No empty name"); } let characterLayers = query.characterLayers; if (characterLayers === null) { - throw new Error('Expecting skin'); + throw new Error("Expecting skin"); } - if (typeof characterLayers === 'string') { - characterLayers = [ characterLayers ]; + if (typeof characterLayers === "string") { + characterLayers = [characterLayers]; } const userUuid = await jwtTokenManager.getUserUuidFromToken(token, IPAddress, roomId); let memberTags: string[] = []; - let memberVisitCardUrl: string|null = null; + let memberVisitCardUrl: string | null = null; let memberMessages: unknown; let memberTextures: CharacterTexture[] = []; const room = await socketManager.getOrCreateRoom(roomId); if (ADMIN_API_URL) { try { - let userData : FetchMemberDataByUuidResponse = { + let userData: FetchMemberDataByUuidResponse = { uuid: v4(), tags: [], visitCardUrl: null, textures: [], messages: [], - anonymous: true + anonymous: true, }; try { userData = await adminApi.fetchMemberDataByUuid(userUuid, roomId); - }catch (err){ + } catch (err) { if (err?.response?.status == 404) { // If we get an HTTP 404, the token is invalid. Let's perform an anonymous login! - console.warn('Cannot find user with uuid "'+userUuid+'". Performing an anonymous login instead.'); - } else if(err?.response?.status == 403) { + console.warn( + 'Cannot find user with uuid "' + + userUuid + + '". Performing an anonymous login instead.' + ); + } else if (err?.response?.status == 403) { // If we get an HTTP 403, the world is full. We need to broadcast a special error to the client. // we finish immediately the upgrade then we will close the socket as soon as it starts opening. - return res.upgrade({ - rejected: true, - message: err?.response?.data.message, - status: err?.response?.status - }, websocketKey, - websocketProtocol, - websocketExtensions, - context); - }else{ + return res.upgrade( + { + rejected: true, + message: err?.response?.data.message, + status: err?.response?.status, + }, + websocketKey, + websocketProtocol, + websocketExtensions, + context + ); + } else { throw err; } } @@ -206,21 +220,30 @@ export class IoSocketController { memberTags = userData.tags; memberVisitCardUrl = userData.visitCardUrl; memberTextures = userData.textures; - if (!room.public && room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && (userData.anonymous === true || !room.canAccess(memberTags))) { - throw new Error('Insufficient privileges to access this room') + if ( + !room.public && + room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && + (userData.anonymous === true || !room.canAccess(memberTags)) + ) { + throw new Error("Insufficient privileges to access this room"); } - if (!room.public && room.policyType === GameRoomPolicyTypes.MEMBERS_ONLY_POLICY && userData.anonymous === true) { - throw new Error('Use the login URL to connect') + if ( + !room.public && + room.policyType === GameRoomPolicyTypes.MEMBERS_ONLY_POLICY && + userData.anonymous === true + ) { + throw new Error("Use the login URL to connect"); } } catch (e) { - console.log('access not granted for user '+userUuid+' and room '+roomId); + console.log("access not granted for user " + userUuid + " and room " + roomId); console.error(e); - throw new Error('User cannot access this world') + throw new Error("User cannot access this world"); } } // Generate characterLayers objects from characterLayers string[] - const characterLayerObjs: CharacterLayer[] = SocketManager.mergeCharacterLayersAndCustomTextures(characterLayers, memberTextures); + const characterLayerObjs: CharacterLayer[] = + SocketManager.mergeCharacterLayersAndCustomTextures(characterLayers, memberTextures); if (upgradeAborted.aborted) { console.log("Ouch! Client disconnected before we could upgrade it!"); @@ -229,7 +252,8 @@ export class IoSocketController { } /* This immediately calls open handler, you must not use res after this call */ - res.upgrade({ + res.upgrade( + { // Data passed here is accessible on the "websocket" socket object. url, token, @@ -246,22 +270,22 @@ export class IoSocketController { position: { x: x, y: y, - direction: 'down', - moving: false + direction: "down", + moving: false, } as PointInterface, viewport: { top, right, bottom, - left - } + left, + }, }, /* Spell these correctly */ websocketKey, websocketProtocol, websocketExtensions, - context); - + context + ); } catch (e) { /*if (e instanceof Error) { console.log(e.message); @@ -269,23 +293,26 @@ export class IoSocketController { } else { res.writeStatus("500 Internal Server Error").end('An error occurred'); }*/ - return res.upgrade({ - rejected: true, - message: e.message ? e.message : '500 Internal Server Error' - }, websocketKey, - websocketProtocol, - websocketExtensions, - context); + return res.upgrade( + { + rejected: true, + message: e.message ? e.message : "500 Internal Server Error", + }, + websocketKey, + websocketProtocol, + websocketExtensions, + context + ); } })(); }, /* Handlers */ open: (ws) => { - if(ws.rejected === true) { + if (ws.rejected === true) { //FIX ME to use status code - if(ws.message === 'World is full'){ + if (ws.message === "World is full") { socketManager.emitWorldFullMessage(ws); - }else{ + } else { socketManager.emitConnexionErrorMessage(ws, ws.message as string); } ws.close(); @@ -299,7 +326,7 @@ export class IoSocketController { //get data information and show messages if (client.messages && Array.isArray(client.messages)) { client.messages.forEach((c: unknown) => { - const messageToSend = c as { type: string, message: string }; + const messageToSend = c as { type: string; message: string }; const sendUserMessage = new SendUserMessage(); sendUserMessage.setType(messageToSend.type); @@ -323,33 +350,48 @@ export class IoSocketController { } else if (message.hasUsermovesmessage()) { socketManager.handleUserMovesMessage(client, message.getUsermovesmessage() as UserMovesMessage); } else if (message.hasSetplayerdetailsmessage()) { - socketManager.handleSetPlayerDetails(client, message.getSetplayerdetailsmessage() as SetPlayerDetailsMessage); + socketManager.handleSetPlayerDetails( + client, + message.getSetplayerdetailsmessage() as SetPlayerDetailsMessage + ); } else if (message.hasSilentmessage()) { socketManager.handleSilentMessage(client, message.getSilentmessage() as SilentMessage); } else if (message.hasItemeventmessage()) { socketManager.handleItemEvent(client, message.getItemeventmessage() as ItemEventMessage); } else if (message.hasWebrtcsignaltoservermessage()) { - socketManager.emitVideo(client, message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage); + socketManager.emitVideo( + client, + message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage + ); } else if (message.hasWebrtcscreensharingsignaltoservermessage()) { - socketManager.emitScreenSharing(client, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage); + socketManager.emitScreenSharing( + client, + message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage + ); } else if (message.hasPlayglobalmessage()) { socketManager.emitPlayGlobalMessage(client, message.getPlayglobalmessage() as PlayGlobalMessage); - } else if (message.hasReportplayermessage()){ + } else if (message.hasReportplayermessage()) { socketManager.handleReportMessage(client, message.getReportplayermessage() as ReportPlayerMessage); - } else if (message.hasQueryjitsijwtmessage()){ - socketManager.handleQueryJitsiJwtMessage(client, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage); - } else if (message.hasEmotepromptmessage()){ - socketManager.handleEmotePromptMessage(client, message.getEmotepromptmessage() as EmotePromptMessage); + } else if (message.hasQueryjitsijwtmessage()) { + socketManager.handleQueryJitsiJwtMessage( + client, + message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage + ); + } else if (message.hasEmotepromptmessage()) { + socketManager.handleEmotePromptMessage( + client, + message.getEmotepromptmessage() as EmotePromptMessage + ); } - /* Ok is false if backpressure was built up, wait for drain */ + /* 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()); + console.log("WebSocket backpressure: " + ws.getBufferedAmount()); }, close: (ws, code, message) => { - const Client = (ws as ExSocketInterface); + const Client = ws as ExSocketInterface; try { Client.disconnecting = true; //leave room @@ -358,13 +400,13 @@ export class IoSocketController { console.error('An error occurred on "disconnect"'); console.error(e); } - } - }) + }, + }); } //eslint-disable-next-line @typescript-eslint/no-explicit-any private initClient(ws: any): ExSocketInterface { - const client : ExSocketInterface = ws; + const client: ExSocketInterface = ws; client.userId = this.nextUserId; this.nextUserId++; client.userUuid = ws.userUuid; @@ -374,7 +416,7 @@ export class IoSocketController { client.batchTimeout = null; client.emitInBatch = (payload: SubMessage): void => { emitInBatch(client, payload); - } + }; client.disconnecting = false; client.messages = ws.messages; diff --git a/pusher/src/Controller/MapController.ts b/pusher/src/Controller/MapController.ts index 1ce04265..1df828d4 100644 --- a/pusher/src/Controller/MapController.ts +++ b/pusher/src/Controller/MapController.ts @@ -1,18 +1,15 @@ -import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js"; -import {BaseController} from "./BaseController"; -import {parse} from "query-string"; -import {adminApi} from "../Services/AdminApi"; +import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js"; +import { BaseController } from "./BaseController"; +import { parse } from "query-string"; +import { adminApi } from "../Services/AdminApi"; - -export class MapController extends BaseController{ - - constructor(private App : TemplatedApp) { +export class MapController extends BaseController { + constructor(private App: TemplatedApp) { super(); this.App = App; this.getMapUrl(); } - // Returns a map mapping map name to file name of the map getMapUrl() { this.App.options("/map", (res: HttpResponse, req: HttpRequest) => { @@ -22,29 +19,28 @@ export class MapController extends BaseController{ }); this.App.get("/map", (res: HttpResponse, req: HttpRequest) => { - res.onAborted(() => { - console.warn('/map request was aborted'); - }) + console.warn("/map request was aborted"); + }); const query = parse(req.getQuery()); - if (typeof query.organizationSlug !== 'string') { - console.error('Expected organizationSlug parameter'); + if (typeof query.organizationSlug !== "string") { + console.error("Expected organizationSlug parameter"); res.writeStatus("400 Bad request"); this.addCorsHeaders(res); res.end("Expected organizationSlug parameter"); return; } - if (typeof query.worldSlug !== 'string') { - console.error('Expected worldSlug parameter'); + if (typeof query.worldSlug !== "string") { + console.error("Expected worldSlug parameter"); res.writeStatus("400 Bad request"); this.addCorsHeaders(res); res.end("Expected worldSlug parameter"); return; } - if (typeof query.roomSlug !== 'string' && query.roomSlug !== undefined) { - console.error('Expected only one roomSlug parameter'); + if (typeof query.roomSlug !== "string" && query.roomSlug !== undefined) { + console.error("Expected only one roomSlug parameter"); res.writeStatus("400 Bad request"); this.addCorsHeaders(res); res.end("Expected only one roomSlug parameter"); @@ -53,7 +49,11 @@ export class MapController extends BaseController{ (async () => { try { - const mapDetails = await adminApi.fetchMapDetails(query.organizationSlug as string, query.worldSlug as string, query.roomSlug as string|undefined); + const mapDetails = await adminApi.fetchMapDetails( + query.organizationSlug as string, + query.worldSlug as string, + query.roomSlug as string | undefined + ); res.writeStatus("200 OK"); this.addCorsHeaders(res); @@ -62,7 +62,6 @@ export class MapController extends BaseController{ this.errorToResponse(e, res); } })(); - }); } } diff --git a/pusher/src/Controller/PrometheusController.ts b/pusher/src/Controller/PrometheusController.ts index e854cf43..3ab3d33f 100644 --- a/pusher/src/Controller/PrometheusController.ts +++ b/pusher/src/Controller/PrometheusController.ts @@ -1,7 +1,7 @@ -import {App} from "../Server/sifrr.server"; -import {HttpRequest, HttpResponse} from "uWebSockets.js"; -const register = require('prom-client').register; -const collectDefaultMetrics = require('prom-client').collectDefaultMetrics; +import { App } from "../Server/sifrr.server"; +import { HttpRequest, HttpResponse } from "uWebSockets.js"; +const register = require("prom-client").register; +const collectDefaultMetrics = require("prom-client").collectDefaultMetrics; export class PrometheusController { constructor(private App: App) { @@ -14,7 +14,7 @@ export class PrometheusController { } private metrics(res: HttpResponse, req: HttpRequest): void { - res.writeHeader('Content-Type', register.contentType); + res.writeHeader("Content-Type", register.contentType); res.end(register.metrics()); } } diff --git a/pusher/src/Enum/EnvironmentVariable.ts b/pusher/src/Enum/EnvironmentVariable.ts index 5b3ec9c4..be974697 100644 --- a/pusher/src/Enum/EnvironmentVariable.ts +++ b/pusher/src/Enum/EnvironmentVariable.ts @@ -1,16 +1,16 @@ const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY"; const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIMUM_DISTANCE) : 64; const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48; -const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == 'true' : false; -const API_URL = process.env.API_URL || ''; -const ADMIN_API_URL = process.env.ADMIN_API_URL || ''; -const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || 'myapitoken'; -const MAX_USERS_PER_ROOM = parseInt(process.env.MAX_USERS_PER_ROOM || '') || 600; +const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == "true" : false; +const API_URL = process.env.API_URL || ""; +const ADMIN_API_URL = process.env.ADMIN_API_URL || ""; +const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "myapitoken"; +const MAX_USERS_PER_ROOM = parseInt(process.env.MAX_USERS_PER_ROOM || "") || 600; const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80; -const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL; -const JITSI_ISS = process.env.JITSI_ISS || ''; -const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || ''; -const PUSHER_HTTP_PORT = parseInt(process.env.PUSHER_HTTP_PORT || '8080') || 8080 +const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL; +const JITSI_ISS = process.env.JITSI_ISS || ""; +const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || ""; +const PUSHER_HTTP_PORT = parseInt(process.env.PUSHER_HTTP_PORT || "8080") || 8080; export const SOCKET_IDLE_TIMER = parseInt(process.env.SOCKET_IDLE_TIMER as string) || 30; // maximum time (in second) without activity before a socket is closed export { @@ -26,5 +26,5 @@ export { JITSI_URL, JITSI_ISS, SECRET_JITSI_KEY, - PUSHER_HTTP_PORT -} + PUSHER_HTTP_PORT, +}; diff --git a/pusher/src/Model/Movable.ts b/pusher/src/Model/Movable.ts index 173db0ae..ca586b7c 100644 --- a/pusher/src/Model/Movable.ts +++ b/pusher/src/Model/Movable.ts @@ -1,8 +1,8 @@ -import {PositionInterface} from "_Model/PositionInterface"; +import { PositionInterface } from "_Model/PositionInterface"; /** * A physical object that can be placed into a Zone */ export interface Movable { - getPosition(): PositionInterface + getPosition(): PositionInterface; } diff --git a/pusher/src/Model/PositionDispatcher.ts b/pusher/src/Model/PositionDispatcher.ts index 1150394b..594328e3 100644 --- a/pusher/src/Model/PositionDispatcher.ts +++ b/pusher/src/Model/PositionDispatcher.ts @@ -8,9 +8,9 @@ * The PositionNotifier is important for performance. It allows us to send the position of players only to a restricted * number of players around the current player. */ -import {Zone, ZoneEventListener} from "./Zone"; -import {ViewportInterface} from "_Model/Websocket/ViewportMessage"; -import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface"; +import { Zone, ZoneEventListener } from "./Zone"; +import { ViewportInterface } from "_Model/Websocket/ViewportMessage"; +import { ExSocketInterface } from "_Model/Websocket/ExSocketInterface"; //import Debug from "debug"; //const debug = Debug('positiondispatcher'); @@ -21,19 +21,22 @@ interface ZoneDescriptor { } export class PositionDispatcher { - // TODO: we need a way to clean the zones if noone is in the zone and noone listening (to free memory!) private zones: Zone[][] = []; - constructor(public readonly roomId: string, private zoneWidth: number, private zoneHeight: number, private socketListener: ZoneEventListener) { - } + constructor( + public readonly roomId: string, + private zoneWidth: number, + private zoneHeight: number, + private socketListener: ZoneEventListener + ) {} private getZoneDescriptorFromCoordinates(x: number, y: number): ZoneDescriptor { return { i: Math.floor(x / this.zoneWidth), j: Math.floor(y / this.zoneHeight), - } + }; } /** @@ -41,7 +44,7 @@ export class PositionDispatcher { */ public setViewport(socket: ExSocketInterface, viewport: ViewportInterface): void { if (viewport.left > viewport.right || viewport.top > viewport.bottom) { - console.warn('Invalid viewport received: ', viewport); + console.warn("Invalid viewport received: ", viewport); return; } @@ -57,8 +60,8 @@ export class PositionDispatcher { } } - const addedZones = [...newZones].filter(x => !oldZones.has(x)); - const removedZones = [...oldZones].filter(x => !newZones.has(x)); + const addedZones = [...newZones].filter((x) => !oldZones.has(x)); + const removedZones = [...oldZones].filter((x) => !newZones.has(x)); for (const zone of addedZones) { zone.startListening(socket); diff --git a/pusher/src/Model/PositionInterface.ts b/pusher/src/Model/PositionInterface.ts index d3b0dd47..65636759 100644 --- a/pusher/src/Model/PositionInterface.ts +++ b/pusher/src/Model/PositionInterface.ts @@ -1,4 +1,4 @@ export interface PositionInterface { - x: number, - y: number + x: number; + y: number; } diff --git a/pusher/src/Model/PusherRoom.ts b/pusher/src/Model/PusherRoom.ts index dcea5859..a49fce3e 100644 --- a/pusher/src/Model/PusherRoom.ts +++ b/pusher/src/Model/PusherRoom.ts @@ -1,9 +1,9 @@ -import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface"; -import {PositionDispatcher} from "./PositionDispatcher"; -import {ViewportInterface} from "_Model/Websocket/ViewportMessage"; -import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "./RoomIdentifier"; -import {arrayIntersect} from "../Services/ArrayHelper"; -import {ZoneEventListener} from "_Model/Zone"; +import { ExSocketInterface } from "_Model/Websocket/ExSocketInterface"; +import { PositionDispatcher } from "./PositionDispatcher"; +import { ViewportInterface } from "_Model/Websocket/ViewportMessage"; +import { extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous } from "./RoomIdentifier"; +import { arrayIntersect } from "../Services/ArrayHelper"; +import { ZoneEventListener } from "_Model/Zone"; export enum GameRoomPolicyTypes { ANONYMUS_POLICY = 1, @@ -17,13 +17,11 @@ export class PusherRoom { public tags: string[]; public policyType: GameRoomPolicyTypes; public readonly roomSlug: string; - public readonly worldSlug: string = ''; - public readonly organizationSlug: string = ''; + public readonly worldSlug: string = ""; + public readonly organizationSlug: string = ""; private versionNumber: number = 1; - constructor(public readonly roomId: string, - private socketListener: ZoneEventListener) - { + constructor(public readonly roomId: string, private socketListener: ZoneEventListener) { this.public = isRoomAnonymous(roomId); this.tags = []; this.policyType = GameRoomPolicyTypes.ANONYMUS_POLICY; @@ -31,7 +29,7 @@ export class PusherRoom { if (this.public) { this.roomSlug = extractRoomSlugPublicRoomId(this.roomId); } else { - const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId(this.roomId); + const { organizationSlug, worldSlug, roomSlug } = extractDataFromPrivateRoomId(this.roomId); this.roomSlug = roomSlug; this.organizationSlug = organizationSlug; this.worldSlug = worldSlug; @@ -41,11 +39,11 @@ export class PusherRoom { this.positionNotifier = new PositionDispatcher(this.roomId, 320, 320, this.socketListener); } - public setViewport(socket : ExSocketInterface, viewport: ViewportInterface): void { + public setViewport(socket: ExSocketInterface, viewport: ViewportInterface): void { this.positionNotifier.setViewport(socket, viewport); } - public leave(socket : ExSocketInterface){ + public leave(socket: ExSocketInterface) { this.positionNotifier.removeViewport(socket); } diff --git a/pusher/src/Model/RoomIdentifier.ts b/pusher/src/Model/RoomIdentifier.ts index 3ac62bca..d1de8800 100644 --- a/pusher/src/Model/RoomIdentifier.ts +++ b/pusher/src/Model/RoomIdentifier.ts @@ -1,30 +1,30 @@ //helper functions to parse room IDs export const isRoomAnonymous = (roomID: string): boolean => { - if (roomID.startsWith('_/')) { + if (roomID.startsWith("_/")) { return true; - } else if(roomID.startsWith('@/')) { + } else if (roomID.startsWith("@/")) { return false; } else { - throw new Error('Incorrect room ID: '+roomID); + throw new Error("Incorrect room ID: " + roomID); } -} +}; export const extractRoomSlugPublicRoomId = (roomId: string): string => { - const idParts = roomId.split('/'); - if (idParts.length < 3) throw new Error('Incorrect roomId: '+roomId); - return idParts.slice(2).join('/'); -} + const idParts = roomId.split("/"); + if (idParts.length < 3) throw new Error("Incorrect roomId: " + roomId); + return idParts.slice(2).join("/"); +}; export interface extractDataFromPrivateRoomIdResponse { organizationSlug: string; worldSlug: string; roomSlug: string; } export const extractDataFromPrivateRoomId = (roomId: string): extractDataFromPrivateRoomIdResponse => { - const idParts = roomId.split('/'); - if (idParts.length < 4) throw new Error('Incorrect roomId: '+roomId); + const idParts = roomId.split("/"); + if (idParts.length < 4) throw new Error("Incorrect roomId: " + roomId); const organizationSlug = idParts[1]; const worldSlug = idParts[2]; const roomSlug = idParts[3]; - return {organizationSlug, worldSlug, roomSlug} -} \ No newline at end of file + return { organizationSlug, worldSlug, roomSlug }; +}; diff --git a/pusher/src/Model/Websocket/ExAdminSocketInterface.ts b/pusher/src/Model/Websocket/ExAdminSocketInterface.ts index 1e03db6c..7599c82c 100644 --- a/pusher/src/Model/Websocket/ExAdminSocketInterface.ts +++ b/pusher/src/Model/Websocket/ExAdminSocketInterface.ts @@ -1,21 +1,22 @@ -import {PointInterface} from "./PointInterface"; -import {Identificable} from "./Identificable"; -import {ViewportInterface} from "_Model/Websocket/ViewportMessage"; +import { PointInterface } from "./PointInterface"; +import { Identificable } from "./Identificable"; +import { ViewportInterface } from "_Model/Websocket/ViewportMessage"; import { AdminPusherToBackMessage, BatchMessage, - PusherToBackMessage, ServerToAdminClientMessage, + PusherToBackMessage, + ServerToAdminClientMessage, ServerToClientMessage, - SubMessage + SubMessage, } from "../../Messages/generated/messages_pb"; -import {WebSocket} from "uWebSockets.js" -import {CharacterTexture} from "../../Services/AdminApi"; -import {ClientDuplexStream} from "grpc"; -import {Zone} from "_Model/Zone"; +import { WebSocket } from "uWebSockets.js"; +import { CharacterTexture } from "../../Services/AdminApi"; +import { ClientDuplexStream } from "grpc"; +import { Zone } from "_Model/Zone"; export type AdminConnection = ClientDuplexStream; export interface ExAdminSocketInterface extends WebSocket { - adminConnection: AdminConnection, - disconnecting: boolean, + adminConnection: AdminConnection; + disconnecting: boolean; } diff --git a/pusher/src/Model/Websocket/ExSocketInterface.ts b/pusher/src/Model/Websocket/ExSocketInterface.ts index 98759142..9a92a0e7 100644 --- a/pusher/src/Model/Websocket/ExSocketInterface.ts +++ b/pusher/src/Model/Websocket/ExSocketInterface.ts @@ -1,23 +1,23 @@ -import {PointInterface} from "./PointInterface"; -import {Identificable} from "./Identificable"; -import {ViewportInterface} from "_Model/Websocket/ViewportMessage"; +import { PointInterface } from "./PointInterface"; +import { Identificable } from "./Identificable"; +import { ViewportInterface } from "_Model/Websocket/ViewportMessage"; import { BatchMessage, CompanionMessage, PusherToBackMessage, ServerToClientMessage, - SubMessage + SubMessage, } from "../../Messages/generated/messages_pb"; -import {WebSocket} from "uWebSockets.js" -import {CharacterTexture} from "../../Services/AdminApi"; -import {ClientDuplexStream} from "grpc"; -import {Zone} from "_Model/Zone"; +import { WebSocket } from "uWebSockets.js"; +import { CharacterTexture } from "../../Services/AdminApi"; +import { ClientDuplexStream } from "grpc"; +import { Zone } from "_Model/Zone"; export type BackConnection = ClientDuplexStream; export interface CharacterLayer { - name: string, - url: string|undefined + name: string; + url: string | undefined; } export interface ExSocketInterface extends WebSocket, Identificable { @@ -36,12 +36,12 @@ export interface ExSocketInterface extends WebSocket, Identificable { */ emitInBatch: (payload: SubMessage) => void; batchedMessages: BatchMessage; - batchTimeout: NodeJS.Timeout|null; - disconnecting: boolean, - messages: unknown, - tags: string[], - visitCardUrl: string|null, - textures: CharacterTexture[], - backConnection: BackConnection, + batchTimeout: NodeJS.Timeout | null; + disconnecting: boolean; + messages: unknown; + tags: string[]; + visitCardUrl: string | null; + textures: CharacterTexture[]; + backConnection: BackConnection; listenedZones: Set; } diff --git a/pusher/src/Model/Websocket/ItemEventMessage.ts b/pusher/src/Model/Websocket/ItemEventMessage.ts index b1f9203e..1bb7f615 100644 --- a/pusher/src/Model/Websocket/ItemEventMessage.ts +++ b/pusher/src/Model/Websocket/ItemEventMessage.ts @@ -1,10 +1,11 @@ import * as tg from "generic-type-guard"; -export const isItemEventMessageInterface = - new tg.IsInterface().withProperties({ +export const isItemEventMessageInterface = new tg.IsInterface() + .withProperties({ itemId: tg.isNumber, event: tg.isString, state: tg.isUnknown, parameters: tg.isUnknown, - }).get(); + }) + .get(); export type ItemEventMessageInterface = tg.GuardedType; diff --git a/pusher/src/Model/Websocket/Point.ts b/pusher/src/Model/Websocket/Point.ts index c66720ba..19b57d2e 100644 --- a/pusher/src/Model/Websocket/Point.ts +++ b/pusher/src/Model/Websocket/Point.ts @@ -1,6 +1,10 @@ -import {PointInterface} from "./PointInterface"; +import { PointInterface } from "./PointInterface"; -export class Point implements PointInterface{ - constructor(public x : number, public y : number, public direction : string = "none", public moving : boolean = false) { - } +export class Point implements PointInterface { + constructor( + public x: number, + public y: number, + public direction: string = "none", + public moving: boolean = false + ) {} } diff --git a/pusher/src/Model/Websocket/PointInterface.ts b/pusher/src/Model/Websocket/PointInterface.ts index afb07a23..d7c7826e 100644 --- a/pusher/src/Model/Websocket/PointInterface.ts +++ b/pusher/src/Model/Websocket/PointInterface.ts @@ -7,11 +7,12 @@ import * as tg from "generic-type-guard"; readonly moving: boolean; }*/ -export const isPointInterface = - new tg.IsInterface().withProperties({ +export const isPointInterface = new tg.IsInterface() + .withProperties({ x: tg.isNumber, y: tg.isNumber, direction: tg.isString, - moving: tg.isBoolean - }).get(); + moving: tg.isBoolean, + }) + .get(); export type PointInterface = tg.GuardedType; diff --git a/pusher/src/Model/Websocket/ProtobufUtils.ts b/pusher/src/Model/Websocket/ProtobufUtils.ts index 89a90acc..bd9cb9c2 100644 --- a/pusher/src/Model/Websocket/ProtobufUtils.ts +++ b/pusher/src/Model/Websocket/ProtobufUtils.ts @@ -1,34 +1,33 @@ -import {PointInterface} from "./PointInterface"; +import { PointInterface } from "./PointInterface"; import { CharacterLayerMessage, ItemEventMessage, PointMessage, - PositionMessage + PositionMessage, } from "../../Messages/generated/messages_pb"; -import {CharacterLayer, ExSocketInterface} from "_Model/Websocket/ExSocketInterface"; +import { CharacterLayer, ExSocketInterface } from "_Model/Websocket/ExSocketInterface"; import Direction = PositionMessage.Direction; -import {ItemEventMessageInterface} from "_Model/Websocket/ItemEventMessage"; -import {PositionInterface} from "_Model/PositionInterface"; +import { ItemEventMessageInterface } from "_Model/Websocket/ItemEventMessage"; +import { PositionInterface } from "_Model/PositionInterface"; export class ProtobufUtils { - public static toPositionMessage(point: PointInterface): PositionMessage { let direction: Direction; switch (point.direction) { - case 'up': + case "up": direction = Direction.UP; break; - case 'down': + case "down": direction = Direction.DOWN; break; - case 'left': + case "left": direction = Direction.LEFT; break; - case 'right': + case "right": direction = Direction.RIGHT; break; default: - throw new Error('unexpected direction'); + throw new Error("unexpected direction"); } const position = new PositionMessage(); @@ -44,16 +43,16 @@ export class ProtobufUtils { let direction: string; switch (position.getDirection()) { case Direction.UP: - direction = 'up'; + direction = "up"; break; case Direction.DOWN: - direction = 'down'; + direction = "down"; break; case Direction.LEFT: - direction = 'left'; + direction = "left"; break; case Direction.RIGHT: - direction = 'right'; + direction = "right"; break; default: throw new Error("Unexpected direction"); @@ -82,7 +81,7 @@ export class ProtobufUtils { event: itemEventMessage.getEvent(), parameters: JSON.parse(itemEventMessage.getParametersjson()), state: JSON.parse(itemEventMessage.getStatejson()), - } + }; } public static toItemEventProtobuf(itemEvent: ItemEventMessageInterface): ItemEventMessage { @@ -96,7 +95,7 @@ export class ProtobufUtils { } public static toCharacterLayerMessages(characterLayers: CharacterLayer[]): CharacterLayerMessage[] { - return characterLayers.map(function(characterLayer): CharacterLayerMessage { + return characterLayers.map(function (characterLayer): CharacterLayerMessage { const message = new CharacterLayerMessage(); message.setName(characterLayer.name); if (characterLayer.url) { diff --git a/pusher/src/Model/Websocket/ViewportMessage.ts b/pusher/src/Model/Websocket/ViewportMessage.ts index 62e2fc81..ea71ad68 100644 --- a/pusher/src/Model/Websocket/ViewportMessage.ts +++ b/pusher/src/Model/Websocket/ViewportMessage.ts @@ -1,10 +1,11 @@ import * as tg from "generic-type-guard"; -export const isViewport = - new tg.IsInterface().withProperties({ +export const isViewport = new tg.IsInterface() + .withProperties({ left: tg.isNumber, top: tg.isNumber, right: tg.isNumber, bottom: tg.isNumber, - }).get(); + }) + .get(); export type ViewportInterface = tg.GuardedType; diff --git a/pusher/src/Model/Zone.ts b/pusher/src/Model/Zone.ts index 318a119b..8eeeb3ef 100644 --- a/pusher/src/Model/Zone.ts +++ b/pusher/src/Model/Zone.ts @@ -1,16 +1,23 @@ -import {ExSocketInterface} from "./Websocket/ExSocketInterface"; -import {apiClientRepository} from "../Services/ApiClientRepository"; +import { ExSocketInterface } from "./Websocket/ExSocketInterface"; +import { apiClientRepository } from "../Services/ApiClientRepository"; import { BatchToPusherMessage, - CharacterLayerMessage, GroupLeftZoneMessage, GroupUpdateMessage, GroupUpdateZoneMessage, - PointMessage, PositionMessage, UserJoinedMessage, - UserJoinedZoneMessage, UserLeftZoneMessage, UserMovedMessage, + CharacterLayerMessage, + GroupLeftZoneMessage, + GroupUpdateMessage, + GroupUpdateZoneMessage, + PointMessage, + PositionMessage, + UserJoinedMessage, + UserJoinedZoneMessage, + UserLeftZoneMessage, + UserMovedMessage, ZoneMessage, EmoteEventMessage, - CompanionMessage + CompanionMessage, } from "../Messages/generated/messages_pb"; -import {ClientReadableStream} from "grpc"; -import {PositionDispatcher} from "_Model/PositionDispatcher"; +import { ClientReadableStream } from "grpc"; +import { PositionDispatcher } from "_Model/PositionDispatcher"; import Debug from "debug"; const debug = Debug("zone"); @@ -30,24 +37,38 @@ export type MovesCallback = (thing: Movable, position: PositionInterface, listen export type LeavesCallback = (thing: Movable, listener: User) => void;*/ export class UserDescriptor { - private constructor(public readonly userId: number, private name: string, private characterLayers: CharacterLayerMessage[], private position: PositionMessage, private visitCardUrl: string | null, private companion?: CompanionMessage) { + private constructor( + public readonly userId: number, + private name: string, + private characterLayers: CharacterLayerMessage[], + private position: PositionMessage, + private visitCardUrl: string | null, + private companion?: CompanionMessage + ) { if (!Number.isInteger(this.userId)) { - throw new Error('UserDescriptor.userId is not an integer: '+this.userId); + throw new Error("UserDescriptor.userId is not an integer: " + this.userId); } } public static createFromUserJoinedZoneMessage(message: UserJoinedZoneMessage): UserDescriptor { const position = message.getPosition(); if (position === undefined) { - throw new Error('Missing position'); + throw new Error("Missing position"); } - return new UserDescriptor(message.getUserid(), message.getName(), message.getCharacterlayersList(), position, message.getVisitcardurl(), message.getCompanion()); + return new UserDescriptor( + message.getUserid(), + message.getName(), + message.getCharacterlayersList(), + position, + message.getVisitcardurl(), + message.getCompanion() + ); } public update(userMovedMessage: UserMovedMessage) { const position = userMovedMessage.getPosition(); if (position === undefined) { - throw new Error('Missing position'); + throw new Error("Missing position"); } this.position = position; } @@ -78,13 +99,12 @@ export class UserDescriptor { } export class GroupDescriptor { - private constructor(public readonly groupId: number, private groupSize: number, private position: PointMessage) { - } + private constructor(public readonly groupId: number, private groupSize: number, private position: PointMessage) {} public static createFromGroupUpdateZoneMessage(message: GroupUpdateZoneMessage): GroupDescriptor { const position = message.getPosition(); if (position === undefined) { - throw new Error('Missing position'); + throw new Error("Missing position"); } return new GroupDescriptor(message.getGroupid(), message.getGroupsize(), position); } @@ -97,7 +117,7 @@ export class GroupDescriptor { public toGroupUpdateMessage(): GroupUpdateMessage { const groupUpdateMessage = new GroupUpdateMessage(); if (!Number.isInteger(this.groupId)) { - throw new Error('GroupDescriptor.groupId is not an integer: '+this.groupId); + throw new Error("GroupDescriptor.groupId is not an integer: " + this.groupId); } groupUpdateMessage.setGroupid(this.groupId); groupUpdateMessage.setGroupsize(this.groupSize); @@ -108,8 +128,8 @@ export class GroupDescriptor { } interface ZoneDescriptor { - x: number, - y: number + x: number; + y: number; } export class Zone { @@ -120,21 +140,26 @@ export class Zone { private backConnection!: ClientReadableStream; private isClosing: boolean = false; - constructor(private positionDispatcher: PositionDispatcher, private socketListener: ZoneEventListener, public readonly x: number, public readonly y: number, private onBackFailure: (e: Error|null, zone: Zone) => void) { - } + constructor( + private positionDispatcher: PositionDispatcher, + private socketListener: ZoneEventListener, + public readonly x: number, + public readonly y: number, + private onBackFailure: (e: Error | null, zone: Zone) => void + ) {} /** * Creates a connection to the back server to track the users. */ public async init(): Promise { - debug('Opening connection to zone %d, %d on back server', this.x, this.y); + debug("Opening connection to zone %d, %d on back server", this.x, this.y); const apiClient = await apiClientRepository.getClient(this.positionDispatcher.roomId); const zoneMessage = new ZoneMessage(); zoneMessage.setRoomid(this.positionDispatcher.roomId); zoneMessage.setX(this.x); zoneMessage.setY(this.y); this.backConnection = apiClient.listenZone(zoneMessage); - this.backConnection.on('data', (batch: BatchToPusherMessage) => { + this.backConnection.on("data", (batch: BatchToPusherMessage) => { for (const message of batch.getPayloadList()) { if (message.hasUserjoinedzonemessage()) { const userJoinedZoneMessage = message.getUserjoinedzonemessage() as UserJoinedZoneMessage; @@ -179,33 +204,32 @@ export class Zone { const userDescriptor = this.users.get(userId); if (userDescriptor === undefined) { - console.error('Unexpected move message received for user "'+userId+'"'); + console.error('Unexpected move message received for user "' + userId + '"'); return; } userDescriptor.update(userMovedMessage); this.notifyUserMove(userDescriptor); - } else if(message.hasEmoteeventmessage()) { + } else if (message.hasEmoteeventmessage()) { const emoteEventMessage = message.getEmoteeventmessage() as EmoteEventMessage; this.notifyEmote(emoteEventMessage); } else { - throw new Error('Unexpected message'); + throw new Error("Unexpected message"); } - } }); - this.backConnection.on('error', (e) => { + this.backConnection.on("error", (e) => { if (!this.isClosing) { - debug('Error on back connection') + debug("Error on back connection"); this.close(); this.onBackFailure(e, this); } }); - this.backConnection.on('close', () => { + this.backConnection.on("close", () => { if (!this.isClosing) { - debug('Close on back connection') + debug("Close on back connection"); this.close(); this.onBackFailure(null, this); } @@ -213,7 +237,7 @@ export class Zone { } public close(): void { - debug('Closing connection to zone %d, %d on back server', this.x, this.y); + debug("Closing connection to zone %d, %d on back server", this.x, this.y); this.isClosing = true; this.backConnection.cancel(); } @@ -225,7 +249,7 @@ export class Zone { /** * Notify listeners of this zone that this user entered */ - private notifyUserEnter(user: UserDescriptor, oldZone: ZoneDescriptor|undefined) { + private notifyUserEnter(user: UserDescriptor, oldZone: ZoneDescriptor | undefined) { for (const listener of this.listeners) { if (listener.userId === user.userId) { continue; @@ -241,7 +265,7 @@ export class Zone { /** * Notify listeners of this zone that this group entered */ - private notifyGroupEnter(group: GroupDescriptor, oldZone: ZoneDescriptor|undefined) { + private notifyGroupEnter(group: GroupDescriptor, oldZone: ZoneDescriptor | undefined) { for (const listener of this.listeners) { if (oldZone === undefined || !this.isListeningZone(listener, oldZone.x, oldZone.y)) { this.socketListener.onGroupEnters(group, listener); @@ -254,7 +278,7 @@ export class Zone { /** * Notify listeners of this zone that this user left */ - private notifyUserLeft(userId: number, newZone: ZoneDescriptor|undefined) { + private notifyUserLeft(userId: number, newZone: ZoneDescriptor | undefined) { for (const listener of this.listeners) { if (listener.userId === userId) { continue; @@ -279,7 +303,7 @@ export class Zone { /** * Notify listeners of this zone that this group left */ - private notifyGroupLeft(groupId: number, newZone: ZoneDescriptor|undefined) { + private notifyGroupLeft(groupId: number, newZone: ZoneDescriptor | undefined) { for (const listener of this.listeners) { if (listener.groupId === groupId) { continue; diff --git a/pusher/src/Server/server/app.ts b/pusher/src/Server/server/app.ts index 3b98a9b3..4c422d5c 100644 --- a/pusher/src/Server/server/app.ts +++ b/pusher/src/Server/server/app.ts @@ -1,13 +1,13 @@ -import { App as _App, AppOptions } from 'uWebSockets.js'; -import BaseApp from './baseapp'; -import { extend } from './utils'; -import { UwsApp } from './types'; +import { App as _App, AppOptions } from "uWebSockets.js"; +import BaseApp from "./baseapp"; +import { extend } from "./utils"; +import { UwsApp } from "./types"; class App extends (_App) { - constructor(options: AppOptions = {}) { - super(options); // eslint-disable-line constructor-super - extend(this, new BaseApp()); - } + constructor(options: AppOptions = {}) { + super(options); // eslint-disable-line constructor-super + extend(this, new BaseApp()); + } } export default App; diff --git a/pusher/src/Server/server/baseapp.ts b/pusher/src/Server/server/baseapp.ts index accd8a99..6d973ac7 100644 --- a/pusher/src/Server/server/baseapp.ts +++ b/pusher/src/Server/server/baseapp.ts @@ -1,116 +1,109 @@ -import { Readable } from 'stream'; -import { us_listen_socket_close, TemplatedApp, HttpResponse, HttpRequest } from 'uWebSockets.js'; +import { Readable } from "stream"; +import { us_listen_socket_close, TemplatedApp, HttpResponse, HttpRequest } from "uWebSockets.js"; -import formData from './formdata'; -import { stob } from './utils'; -import { Handler } from './types'; -import {join} from "path"; +import formData from "./formdata"; +import { stob } from "./utils"; +import { Handler } from "./types"; +import { join } from "path"; -const contTypes = ['application/x-www-form-urlencoded', 'multipart/form-data']; +const contTypes = ["application/x-www-form-urlencoded", "multipart/form-data"]; const noOp = () => true; const handleBody = (res: HttpResponse, req: HttpRequest) => { - const contType = req.getHeader('content-type'); + const contType = req.getHeader("content-type"); - res.bodyStream = function() { - const stream = new Readable(); - stream._read = noOp; // eslint-disable-line @typescript-eslint/unbound-method + res.bodyStream = function () { + const stream = new Readable(); + stream._read = noOp; // eslint-disable-line @typescript-eslint/unbound-method - this.onData((ab: ArrayBuffer, isLast: boolean) => { - // uint and then slicing is bit faster than slice and then uint - stream.push(new Uint8Array(ab.slice((ab as any).byteOffset, ab.byteLength))); // eslint-disable-line @typescript-eslint/no-explicit-any - if (isLast) { - stream.push(null); - } - }); + this.onData((ab: ArrayBuffer, isLast: boolean) => { + // uint and then slicing is bit faster than slice and then uint + stream.push(new Uint8Array(ab.slice((ab as any).byteOffset, ab.byteLength))); // eslint-disable-line @typescript-eslint/no-explicit-any + if (isLast) { + stream.push(null); + } + }); - return stream; - }; + return stream; + }; - res.body = () => stob(res.bodyStream()); + res.body = () => stob(res.bodyStream()); - if (contType.includes('application/json')) - res.json = async () => JSON.parse(await res.body()); - if (contTypes.map(t => contType.includes(t)).includes(true)) - res.formData = formData.bind(res, contType); + if (contType.includes("application/json")) res.json = async () => JSON.parse(await res.body()); + if (contTypes.map((t) => contType.includes(t)).includes(true)) res.formData = formData.bind(res, contType); }; class BaseApp { - _sockets = new Map(); - ws!: TemplatedApp['ws']; - get!: TemplatedApp['get']; - _post!: TemplatedApp['post']; - _put!: TemplatedApp['put']; - _patch!: TemplatedApp['patch']; - _listen!: TemplatedApp['listen']; + _sockets = new Map(); + ws!: TemplatedApp["ws"]; + get!: TemplatedApp["get"]; + _post!: TemplatedApp["post"]; + _put!: TemplatedApp["put"]; + _patch!: TemplatedApp["patch"]; + _listen!: TemplatedApp["listen"]; - post(pattern: string, handler: Handler) { - if (typeof handler !== 'function') - throw Error(`handler should be a function, given ${typeof handler}.`); - this._post(pattern, (res, req) => { - handleBody(res, req); - handler(res, req); - }); - return this; - } + post(pattern: string, handler: Handler) { + if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`); + this._post(pattern, (res, req) => { + handleBody(res, req); + handler(res, req); + }); + return this; + } - put(pattern: string, handler: Handler) { - if (typeof handler !== 'function') - throw Error(`handler should be a function, given ${typeof handler}.`); - this._put(pattern, (res, req) => { - handleBody(res, req); + put(pattern: string, handler: Handler) { + if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`); + this._put(pattern, (res, req) => { + handleBody(res, req); - handler(res, req); - }); - return this; - } + handler(res, req); + }); + return this; + } - patch(pattern: string, handler: Handler) { - if (typeof handler !== 'function') - throw Error(`handler should be a function, given ${typeof handler}.`); - this._patch(pattern, (res, req) => { - handleBody(res, req); + patch(pattern: string, handler: Handler) { + if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`); + this._patch(pattern, (res, req) => { + handleBody(res, req); - handler(res, req); - }); - return this; - } + handler(res, req); + }); + return this; + } - listen(h: string | number, p: Function | number = noOp, cb?: Function) { - if (typeof p === 'number' && typeof h === 'string') { - this._listen(h, p, socket => { - this._sockets.set(p, socket); - if (cb === undefined) { - throw new Error('cb undefined'); + listen(h: string | number, p: Function | number = noOp, cb?: Function) { + if (typeof p === "number" && typeof h === "string") { + this._listen(h, p, (socket) => { + this._sockets.set(p, socket); + if (cb === undefined) { + throw new Error("cb undefined"); + } + cb(socket); + }); + } else if (typeof h === "number" && typeof p === "function") { + this._listen(h, (socket) => { + this._sockets.set(h, socket); + p(socket); + }); + } else { + throw Error("Argument types: (host: string, port: number, cb?: Function) | (port: number, cb?: Function)"); } - cb(socket); - }); - } else if (typeof h === 'number' && typeof p === 'function') { - this._listen(h, socket => { - this._sockets.set(h, socket); - p(socket); - }); - } else { - throw Error( - 'Argument types: (host: string, port: number, cb?: Function) | (port: number, cb?: Function)' - ); + + return this; } - return this; - } - - close(port: null | number = null) { - if (port) { - this._sockets.has(port) && us_listen_socket_close(this._sockets.get(port)); - this._sockets.delete(port); - } else { - this._sockets.forEach(app => { - us_listen_socket_close(app); - }); - this._sockets.clear(); + close(port: null | number = null) { + if (port) { + this._sockets.has(port) && us_listen_socket_close(this._sockets.get(port)); + this._sockets.delete(port); + } else { + this._sockets.forEach((app) => { + us_listen_socket_close(app); + }); + this._sockets.clear(); + } + return this; } - return this; - } } export default BaseApp; diff --git a/pusher/src/Server/server/formdata.ts b/pusher/src/Server/server/formdata.ts index 9dd08440..66e51db4 100644 --- a/pusher/src/Server/server/formdata.ts +++ b/pusher/src/Server/server/formdata.ts @@ -1,100 +1,99 @@ -import { createWriteStream } from 'fs'; -import { join, dirname } from 'path'; -import Busboy from 'busboy'; -import mkdirp from 'mkdirp'; +import { createWriteStream } from "fs"; +import { join, dirname } from "path"; +import Busboy from "busboy"; +import mkdirp from "mkdirp"; function formData( - contType: string, - options: busboy.BusboyConfig & { - abortOnLimit?: boolean; - tmpDir?: string; - onFile?: ( - fieldname: string, - file: NodeJS.ReadableStream, - filename: string, - encoding: string, - mimetype: string - ) => string; - onField?: (fieldname: string, value: any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any - filename?: (oldName: string) => string; - } = {} + contType: string, + options: busboy.BusboyConfig & { + abortOnLimit?: boolean; + tmpDir?: string; + onFile?: ( + fieldname: string, + file: NodeJS.ReadableStream, + filename: string, + encoding: string, + mimetype: string + ) => string; + onField?: (fieldname: string, value: any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any + filename?: (oldName: string) => string; + } = {} ) { - console.log('Enter form data'); - options.headers = { - 'content-type': contType - }; + console.log("Enter form data"); + options.headers = { + "content-type": contType, + }; - return new Promise((resolve, reject) => { - const busb = new Busboy(options); - const ret = {}; + return new Promise((resolve, reject) => { + const busb = new Busboy(options); + const ret = {}; - this.bodyStream().pipe(busb); + this.bodyStream().pipe(busb); - busb.on('limit', () => { - if (options.abortOnLimit) { - reject(Error('limit')); - } + busb.on("limit", () => { + if (options.abortOnLimit) { + reject(Error("limit")); + } + }); + + busb.on("file", function (fieldname, file, filename, encoding, mimetype) { + const value: { filePath: string | undefined; filename: string; encoding: string; mimetype: string } = { + filename, + encoding, + mimetype, + filePath: undefined, + }; + + if (typeof options.tmpDir === "string") { + if (typeof options.filename === "function") filename = options.filename(filename); + const fileToSave = join(options.tmpDir, filename); + mkdirp(dirname(fileToSave)); + + file.pipe(createWriteStream(fileToSave)); + value.filePath = fileToSave; + } + if (typeof options.onFile === "function") { + value.filePath = options.onFile(fieldname, file, filename, encoding, mimetype) || value.filePath; + } + + setRetValue(ret, fieldname, value); + }); + + busb.on("field", function (fieldname, value) { + if (typeof options.onField === "function") options.onField(fieldname, value); + + setRetValue(ret, fieldname, value); + }); + + busb.on("finish", function () { + resolve(ret); + }); + + busb.on("error", reject); }); - - busb.on('file', function(fieldname, file, filename, encoding, mimetype) { - const value: { filePath: string|undefined, filename: string, encoding:string, mimetype: string } = { - filename, - encoding, - mimetype, - filePath: undefined - }; - - if (typeof options.tmpDir === 'string') { - if (typeof options.filename === 'function') filename = options.filename(filename); - const fileToSave = join(options.tmpDir, filename); - mkdirp(dirname(fileToSave)); - - file.pipe(createWriteStream(fileToSave)); - value.filePath = fileToSave; - } - if (typeof options.onFile === 'function') { - value.filePath = - options.onFile(fieldname, file, filename, encoding, mimetype) || value.filePath; - } - - setRetValue(ret, fieldname, value); - }); - - busb.on('field', function(fieldname, value) { - if (typeof options.onField === 'function') options.onField(fieldname, value); - - setRetValue(ret, fieldname, value); - }); - - busb.on('finish', function() { - resolve(ret); - }); - - busb.on('error', reject); - }); } function setRetValue( - ret: { [x: string]: any }, // eslint-disable-line @typescript-eslint/no-explicit-any - fieldname: string, - value: { filename: string; encoding: string; mimetype: string; filePath?: string } | any // eslint-disable-line @typescript-eslint/no-explicit-any + ret: { [x: string]: any }, // eslint-disable-line @typescript-eslint/no-explicit-any + fieldname: string, + value: { filename: string; encoding: string; mimetype: string; filePath?: string } | any // eslint-disable-line @typescript-eslint/no-explicit-any ) { - if (fieldname.endsWith('[]')) { - fieldname = fieldname.slice(0, fieldname.length - 2); - if (Array.isArray(ret[fieldname])) { - ret[fieldname].push(value); + if (fieldname.endsWith("[]")) { + fieldname = fieldname.slice(0, fieldname.length - 2); + if (Array.isArray(ret[fieldname])) { + ret[fieldname].push(value); + } else { + ret[fieldname] = [value]; + } } else { - ret[fieldname] = [value]; + if (Array.isArray(ret[fieldname])) { + ret[fieldname].push(value); + } else if (ret[fieldname]) { + ret[fieldname] = [ret[fieldname], value]; + } else { + ret[fieldname] = value; + } } - } else { - if (Array.isArray(ret[fieldname])) { - ret[fieldname].push(value); - } else if (ret[fieldname]) { - ret[fieldname] = [ret[fieldname], value]; - } else { - ret[fieldname] = value; - } - } } export default formData; diff --git a/pusher/src/Server/server/sslapp.ts b/pusher/src/Server/server/sslapp.ts index 46ae89a5..80df0e4a 100644 --- a/pusher/src/Server/server/sslapp.ts +++ b/pusher/src/Server/server/sslapp.ts @@ -1,13 +1,13 @@ -import { SSLApp as _SSLApp, AppOptions } from 'uWebSockets.js'; -import BaseApp from './baseapp'; -import { extend } from './utils'; -import { UwsApp } from './types'; +import { SSLApp as _SSLApp, AppOptions } from "uWebSockets.js"; +import BaseApp from "./baseapp"; +import { extend } from "./utils"; +import { UwsApp } from "./types"; class SSLApp extends (_SSLApp) { - constructor(options: AppOptions) { - super(options); // eslint-disable-line constructor-super - extend(this, new BaseApp()); - } + constructor(options: AppOptions) { + super(options); // eslint-disable-line constructor-super + extend(this, new BaseApp()); + } } export default SSLApp; diff --git a/pusher/src/Server/server/types.ts b/pusher/src/Server/server/types.ts index 3d0f48c7..afc21d17 100644 --- a/pusher/src/Server/server/types.ts +++ b/pusher/src/Server/server/types.ts @@ -1,9 +1,9 @@ -import { AppOptions, TemplatedApp, HttpResponse, HttpRequest } from 'uWebSockets.js'; +import { AppOptions, TemplatedApp, HttpResponse, HttpRequest } from "uWebSockets.js"; export type UwsApp = { - (options: AppOptions): TemplatedApp; - new (options: AppOptions): TemplatedApp; - prototype: TemplatedApp; + (options: AppOptions): TemplatedApp; + new (options: AppOptions): TemplatedApp; + prototype: TemplatedApp; }; export type Handler = (res: HttpResponse, req: HttpRequest) => void; diff --git a/pusher/src/Server/server/utils.ts b/pusher/src/Server/server/utils.ts index 80ea3938..9816de54 100644 --- a/pusher/src/Server/server/utils.ts +++ b/pusher/src/Server/server/utils.ts @@ -1,37 +1,36 @@ -import { ReadStream } from 'fs'; +import { ReadStream } from "fs"; -function extend(who: any, from: any, overwrite = true) { // eslint-disable-line @typescript-eslint/no-explicit-any - const ownProps = Object.getOwnPropertyNames(Object.getPrototypeOf(from)).concat( - Object.keys(from) - ); - ownProps.forEach(prop => { - if (prop === 'constructor' || from[prop] === undefined) return; - if (who[prop] && overwrite) { - who[`_${prop}`] = who[prop]; - } - if (typeof from[prop] === 'function') who[prop] = from[prop].bind(who); - else who[prop] = from[prop]; - }); +function extend(who: any, from: any, overwrite = true) { + // eslint-disable-line @typescript-eslint/no-explicit-any + const ownProps = Object.getOwnPropertyNames(Object.getPrototypeOf(from)).concat(Object.keys(from)); + ownProps.forEach((prop) => { + if (prop === "constructor" || from[prop] === undefined) return; + if (who[prop] && overwrite) { + who[`_${prop}`] = who[prop]; + } + if (typeof from[prop] === "function") who[prop] = from[prop].bind(who); + else who[prop] = from[prop]; + }); } function stob(stream: ReadStream): Promise { - return new Promise(resolve => { - const buffers: Buffer[] = []; - stream.on('data', buffers.push.bind(buffers)); + return new Promise((resolve) => { + const buffers: Buffer[] = []; + stream.on("data", buffers.push.bind(buffers)); - stream.on('end', () => { - switch (buffers.length) { - case 0: - resolve(Buffer.allocUnsafe(0)); - break; - case 1: - resolve(buffers[0]); - break; - default: - resolve(Buffer.concat(buffers)); - } + stream.on("end", () => { + switch (buffers.length) { + case 0: + resolve(Buffer.allocUnsafe(0)); + break; + case 1: + resolve(buffers[0]); + break; + default: + resolve(Buffer.concat(buffers)); + } + }); }); - }); } export { extend, stob }; diff --git a/pusher/src/Server/sifrr.server.ts b/pusher/src/Server/sifrr.server.ts index 47fba02c..4ef03721 100644 --- a/pusher/src/Server/sifrr.server.ts +++ b/pusher/src/Server/sifrr.server.ts @@ -1,19 +1,19 @@ -import { parse } from 'query-string'; -import { HttpRequest } from 'uWebSockets.js'; -import App from './server/app'; -import SSLApp from './server/sslapp'; -import * as types from './server/types'; +import { parse } from "query-string"; +import { HttpRequest } from "uWebSockets.js"; +import App from "./server/app"; +import SSLApp from "./server/sslapp"; +import * as types from "./server/types"; const getQuery = (req: HttpRequest) => { - return parse(req.getQuery()); + return parse(req.getQuery()); }; export { App, SSLApp, getQuery }; -export * from './server/types'; +export * from "./server/types"; export default { - App, - SSLApp, - getQuery, - ...types + App, + SSLApp, + getQuery, + ...types, }; diff --git a/pusher/src/Services/AdminApi.ts b/pusher/src/Services/AdminApi.ts index fbd7a070..2cbac52c 100644 --- a/pusher/src/Services/AdminApi.ts +++ b/pusher/src/Services/AdminApi.ts @@ -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 { + async fetchMapDetails( + organizationSlug: string, + worldSlug: string, + roomSlug: string | undefined + ): Promise { 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 { 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 { 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 { 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 { + async verifyBanUser( + organizationMemberToken: string, + ipAddress: string, + organization: string, + world: string + ): Promise { 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; }); diff --git a/pusher/src/Services/ApiClientRepository.ts b/pusher/src/Services/ApiClientRepository.ts index c1c6bd38..59e181ae 100644 --- a/pusher/src/Services/ApiClientRepository.ts +++ b/pusher/src/Services/ApiClientRepository.ts @@ -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 { - 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 { - return [await this.getClient('')]; + return [await this.getClient("")]; } } -const apiClientRepository = new ApiClientRepository(API_URL.split(',')); +const apiClientRepository = new ApiClientRepository(API_URL.split(",")); export { apiClientRepository }; diff --git a/pusher/src/Services/ArrayHelper.ts b/pusher/src/Services/ArrayHelper.ts index 67321d1b..8af1da9f 100644 --- a/pusher/src/Services/ArrayHelper.ts +++ b/pusher/src/Services/ArrayHelper.ts @@ -1,3 +1,3 @@ -export const arrayIntersect = (array1: string[], array2: string[]) : boolean => { - return array1.filter(value => array2.includes(value)).length > 0; -} \ No newline at end of file +export const arrayIntersect = (array1: string[], array2: string[]): boolean => { + return array1.filter((value) => array2.includes(value)).length > 0; +}; diff --git a/pusher/src/Services/ClientEventsEmitter.ts b/pusher/src/Services/ClientEventsEmitter.ts index 7b888ef6..0f56d55c 100644 --- a/pusher/src/Services/ClientEventsEmitter.ts +++ b/pusher/src/Services/ClientEventsEmitter.ts @@ -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(); \ No newline at end of file +export const clientEventsEmitter = new ClientEventsEmitter(); diff --git a/pusher/src/Services/CpuTracker.ts b/pusher/src/Services/CpuTracker.ts index c7d57f3d..3d06ca70 100644 --- a/pusher/src/Services/CpuTracker.ts +++ b/pusher/src/Services/CpuTracker.ts @@ -1,6 +1,6 @@ -import {CPU_OVERHEAT_THRESHOLD} from "../Enum/EnvironmentVariable"; +import { CPU_OVERHEAT_THRESHOLD } from "../Enum/EnvironmentVariable"; -function secNSec2ms(secNSec: Array|number) { +function secNSec2ms(secNSec: Array | 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; diff --git a/pusher/src/Services/GaugeManager.ts b/pusher/src/Services/GaugeManager.ts index f8af822b..9780d89d 100644 --- a/pusher/src/Services/GaugeManager.ts +++ b/pusher/src/Services/GaugeManager.ts @@ -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(); \ No newline at end of file +export const gaugeManager = new GaugeManager(); diff --git a/pusher/src/Services/IoSocketHelpers.ts b/pusher/src/Services/IoSocketHelpers.ts index e90e0874..2da7c430 100644 --- a/pusher/src/Services/IoSocketHelpers.ts +++ b/pusher/src/Services/IoSocketHelpers.ts @@ -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); } - diff --git a/pusher/src/Services/JWTTokenManager.ts b/pusher/src/Services/JWTTokenManager.ts index 68d5488a..2e82929e 100644 --- a/pusher/src/Services/JWTTokenManager.ts +++ b/pusher/src/Services/JWTTokenManager.ts @@ -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 { - 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((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 { - 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(); diff --git a/pusher/src/Services/SocketManager.ts b/pusher/src/Services/SocketManager.ts index 319d2b5e..8a0d3673 100644 --- a/pusher/src/Services/SocketManager.ts +++ b/pusher/src/Services/SocketManager.ts @@ -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 = new Map(); private sockets: Map = new Map(); @@ -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 { 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 { //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 { - 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 { 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); }