Merge pull request #1226 from thecodingmachine/apply_prettier_on_push_and_back
Applying prettier in CI for pusher and back containers
This commit is contained in:
commit
52a90e52d4
13
.github/workflows/continuous_integration.yml
vendored
13
.github/workflows/continuous_integration.yml
vendored
@ -64,6 +64,11 @@ jobs:
|
|||||||
run: yarn test
|
run: yarn test
|
||||||
working-directory: "front"
|
working-directory: "front"
|
||||||
|
|
||||||
|
# We will enable prettier checks on front in a few month, when most PRs without prettier have been merged
|
||||||
|
# - name: "Prettier"
|
||||||
|
# run: yarn run pretty-check
|
||||||
|
# working-directory: "front"
|
||||||
|
|
||||||
continuous-integration-pusher:
|
continuous-integration-pusher:
|
||||||
name: "Continuous Integration Pusher"
|
name: "Continuous Integration Pusher"
|
||||||
|
|
||||||
@ -107,6 +112,10 @@ jobs:
|
|||||||
run: yarn test
|
run: yarn test
|
||||||
working-directory: "pusher"
|
working-directory: "pusher"
|
||||||
|
|
||||||
|
- name: "Prettier"
|
||||||
|
run: yarn run pretty-check
|
||||||
|
working-directory: "pusher"
|
||||||
|
|
||||||
continuous-integration-back:
|
continuous-integration-back:
|
||||||
name: "Continuous Integration Back"
|
name: "Continuous Integration Back"
|
||||||
|
|
||||||
@ -150,3 +159,7 @@ jobs:
|
|||||||
run: yarn test
|
run: yarn test
|
||||||
working-directory: "back"
|
working-directory: "back"
|
||||||
|
|
||||||
|
- name: "Prettier"
|
||||||
|
run: yarn run pretty-check
|
||||||
|
working-directory: "back"
|
||||||
|
|
||||||
|
@ -46,6 +46,15 @@ $ yarn run install
|
|||||||
$ yarn run prepare
|
$ yarn run prepare
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you don't have the precommit hook installed (or if you committed code before installing the precommit hook), you will need
|
||||||
|
to run code linting manually:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker-compose exec front yarn run pretty
|
||||||
|
$ docker-compose exec pusher yarn run pretty
|
||||||
|
$ docker-compose exec back yarn run pretty
|
||||||
|
```
|
||||||
|
|
||||||
### Providing tests
|
### Providing tests
|
||||||
|
|
||||||
WorkAdventure is based on a video game engine (Phaser), and video games are not the easiest programs to unit test.
|
WorkAdventure is based on a video game engine (Phaser), and video games are not the easiest programs to unit test.
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
"runprod": "node --max-old-space-size=4096 ./dist/server.js",
|
"runprod": "node --max-old-space-size=4096 ./dist/server.js",
|
||||||
"profile": "tsc && node --prof ./dist/server.js",
|
"profile": "tsc && node --prof ./dist/server.js",
|
||||||
"test": "ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
|
"test": "ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
|
||||||
"lint": "node_modules/.bin/eslint src/ . --ext .ts",
|
"lint": "DEBUG= node_modules/.bin/eslint src/ . --ext .ts",
|
||||||
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts",
|
"fix": "DEBUG= node_modules/.bin/eslint --fix src/ . --ext .ts",
|
||||||
"precommit": "lint-staged",
|
"precommit": "lint-staged",
|
||||||
"pretty": "yarn prettier --write 'src/**/*.{ts,tsx}'",
|
"pretty": "yarn prettier --write 'src/**/*.{ts,tsx}'",
|
||||||
"pretty-check": "yarn prettier --check 'src/**/*.{ts,tsx}'"
|
"pretty-check": "yarn prettier --check 'src/**/*.{ts,tsx}'"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// lib/app.ts
|
// lib/app.ts
|
||||||
import {PrometheusController} from "./Controller/PrometheusController";
|
import { PrometheusController } from "./Controller/PrometheusController";
|
||||||
import {DebugController} from "./Controller/DebugController";
|
import { DebugController } from "./Controller/DebugController";
|
||||||
import {App as uwsApp} from "./Server/sifrr.server";
|
import { App as uwsApp } from "./Server/sifrr.server";
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
public app: uwsApp;
|
public app: uwsApp;
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import {HttpResponse} from "uWebSockets.js";
|
import { HttpResponse } from "uWebSockets.js";
|
||||||
|
|
||||||
|
|
||||||
export class BaseController {
|
export class BaseController {
|
||||||
protected addCorsHeaders(res: HttpResponse): void {
|
protected addCorsHeaders(res: HttpResponse): void {
|
||||||
res.writeHeader('access-control-allow-headers', 'Origin, X-Requested-With, Content-Type, Accept');
|
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-methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
|
||||||
res.writeHeader('access-control-allow-origin', '*');
|
res.writeHeader("access-control-allow-origin", "*");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,53 +1,54 @@
|
|||||||
import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_TOKEN } from "../Enum/EnvironmentVariable";
|
||||||
import {stringify} from "circular-json";
|
import { stringify } from "circular-json";
|
||||||
import {HttpRequest, HttpResponse} from "uWebSockets.js";
|
import { HttpRequest, HttpResponse } from "uWebSockets.js";
|
||||||
import { parse } from 'query-string';
|
import { parse } from "query-string";
|
||||||
import {App} from "../Server/sifrr.server";
|
import { App } from "../Server/sifrr.server";
|
||||||
import {socketManager} from "../Services/SocketManager";
|
import { socketManager } from "../Services/SocketManager";
|
||||||
|
|
||||||
export class DebugController {
|
export class DebugController {
|
||||||
constructor(private App : App) {
|
constructor(private App: App) {
|
||||||
this.getDump();
|
this.getDump();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDump() {
|
||||||
getDump(){
|
|
||||||
this.App.get("/dump", (res: HttpResponse, req: HttpRequest) => {
|
this.App.get("/dump", (res: HttpResponse, req: HttpRequest) => {
|
||||||
const query = parse(req.getQuery());
|
const query = parse(req.getQuery());
|
||||||
|
|
||||||
if (query.token !== ADMIN_API_TOKEN) {
|
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(
|
return res
|
||||||
socketManager.getWorlds(),
|
.writeStatus("200 OK")
|
||||||
(key: unknown, value: unknown) => {
|
.writeHeader("Content-Type", "application/json")
|
||||||
if (key === 'listeners') {
|
.end(
|
||||||
return 'Listeners';
|
stringify(socketManager.getWorlds(), (key: unknown, value: unknown) => {
|
||||||
}
|
if (key === "listeners") {
|
||||||
if (key === 'socket') {
|
return "Listeners";
|
||||||
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;
|
if (key === "socket") {
|
||||||
} else if(value instanceof Set) {
|
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<unknown> = [];
|
const obj: Array<unknown> = [];
|
||||||
for (const [setKey, setValue] of value.entries()) {
|
for (const [setKey, setValue] of value.entries()) {
|
||||||
obj.push(setValue);
|
obj.push(setValue);
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
));
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {App} from "../Server/sifrr.server";
|
import { App } from "../Server/sifrr.server";
|
||||||
import {HttpRequest, HttpResponse} from "uWebSockets.js";
|
import { HttpRequest, HttpResponse } from "uWebSockets.js";
|
||||||
const register = require('prom-client').register;
|
const register = require("prom-client").register;
|
||||||
const collectDefaultMetrics = require('prom-client').collectDefaultMetrics;
|
const collectDefaultMetrics = require("prom-client").collectDefaultMetrics;
|
||||||
|
|
||||||
export class PrometheusController {
|
export class PrometheusController {
|
||||||
constructor(private App: App) {
|
constructor(private App: App) {
|
||||||
@ -14,7 +14,7 @@ export class PrometheusController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private metrics(res: HttpResponse, req: HttpRequest): void {
|
private metrics(res: HttpResponse, req: HttpRequest): void {
|
||||||
res.writeHeader('Content-Type', register.contentType);
|
res.writeHeader("Content-Type", register.contentType);
|
||||||
res.end(register.metrics());
|
res.end(register.metrics());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIMUM_DISTANCE) : 64;
|
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 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 ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == "true" : false;
|
||||||
const ADMIN_API_URL = process.env.ADMIN_API_URL || '';
|
const ADMIN_API_URL = process.env.ADMIN_API_URL || "";
|
||||||
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || 'myapitoken';
|
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "myapitoken";
|
||||||
const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80;
|
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_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
|
||||||
const JITSI_ISS = process.env.JITSI_ISS || '';
|
const JITSI_ISS = process.env.JITSI_ISS || "";
|
||||||
const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || '';
|
const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || "";
|
||||||
const HTTP_PORT = parseInt(process.env.HTTP_PORT || '8080') || 8080;
|
const HTTP_PORT = parseInt(process.env.HTTP_PORT || "8080") || 8080;
|
||||||
const GRPC_PORT = parseInt(process.env.GRPC_PORT || '50051') || 50051;
|
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 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 TURN_STATIC_AUTH_SECRET = process.env.TURN_STATIC_AUTH_SECRET || "";
|
||||||
export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || '4');
|
export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || "4");
|
||||||
|
|
||||||
export {
|
export {
|
||||||
MINIMUM_DISTANCE,
|
MINIMUM_DISTANCE,
|
||||||
@ -24,5 +24,5 @@ export {
|
|||||||
CPU_OVERHEAT_THRESHOLD,
|
CPU_OVERHEAT_THRESHOLD,
|
||||||
JITSI_URL,
|
JITSI_URL,
|
||||||
JITSI_ISS,
|
JITSI_ISS,
|
||||||
SECRET_JITSI_KEY
|
SECRET_JITSI_KEY,
|
||||||
}
|
};
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
ServerToAdminClientMessage,
|
ServerToAdminClientMessage,
|
||||||
UserJoinedRoomMessage, UserLeftRoomMessage
|
UserJoinedRoomMessage,
|
||||||
|
UserLeftRoomMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import {AdminSocket} from "../RoomManager";
|
import { AdminSocket } from "../RoomManager";
|
||||||
|
|
||||||
|
|
||||||
export class Admin {
|
export class Admin {
|
||||||
public constructor(
|
public constructor(private readonly socket: AdminSocket) {}
|
||||||
private readonly socket: AdminSocket
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendUserJoin(uuid: string, name: string, ip: string): void {
|
public sendUserJoin(uuid: string, name: string, ip: string): void {
|
||||||
const serverToAdminClientMessage = new ServerToAdminClientMessage();
|
const serverToAdminClientMessage = new ServerToAdminClientMessage();
|
||||||
@ -24,7 +21,7 @@ export class Admin {
|
|||||||
this.socket.write(serverToAdminClientMessage);
|
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 serverToAdminClientMessage = new ServerToAdminClientMessage();
|
||||||
|
|
||||||
const userLeftRoomMessage = new UserLeftRoomMessage();
|
const userLeftRoomMessage = new UserLeftRoomMessage();
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import {PointInterface} from "./Websocket/PointInterface";
|
import { PointInterface } from "./Websocket/PointInterface";
|
||||||
import {Group} from "./Group";
|
import { Group } from "./Group";
|
||||||
import {User, UserSocket} from "./User";
|
import { User, UserSocket } from "./User";
|
||||||
import {PositionInterface} from "_Model/PositionInterface";
|
import { PositionInterface } from "_Model/PositionInterface";
|
||||||
import {EmoteCallback, EntersCallback, LeavesCallback, MovesCallback} from "_Model/Zone";
|
import { EmoteCallback, EntersCallback, LeavesCallback, MovesCallback } from "_Model/Zone";
|
||||||
import {PositionNotifier} from "./PositionNotifier";
|
import { PositionNotifier } from "./PositionNotifier";
|
||||||
import {Movable} from "_Model/Movable";
|
import { Movable } from "_Model/Movable";
|
||||||
import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "./RoomIdentifier";
|
import { extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous } from "./RoomIdentifier";
|
||||||
import {arrayIntersect} from "../Services/ArrayHelper";
|
import { arrayIntersect } from "../Services/ArrayHelper";
|
||||||
import {EmoteEventMessage, JoinRoomMessage} from "../Messages/generated/messages_pb";
|
import { EmoteEventMessage, JoinRoomMessage } from "../Messages/generated/messages_pb";
|
||||||
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
||||||
import {ZoneSocket} from "src/RoomManager";
|
import { ZoneSocket } from "src/RoomManager";
|
||||||
import {Admin} from "../Model/Admin";
|
import { Admin } from "../Model/Admin";
|
||||||
|
|
||||||
export type ConnectCallback = (user: User, group: Group) => void;
|
export type ConnectCallback = (user: User, group: Group) => void;
|
||||||
export type DisconnectCallback = (user: User, group: Group) => void;
|
export type DisconnectCallback = (user: User, group: Group) => void;
|
||||||
@ -39,33 +39,33 @@ export class GameRoom {
|
|||||||
private readonly positionNotifier: PositionNotifier;
|
private readonly positionNotifier: PositionNotifier;
|
||||||
public readonly roomId: string;
|
public readonly roomId: string;
|
||||||
public readonly roomSlug: string;
|
public readonly roomSlug: string;
|
||||||
public readonly worldSlug: string = '';
|
public readonly worldSlug: string = "";
|
||||||
public readonly organizationSlug: string = '';
|
public readonly organizationSlug: string = "";
|
||||||
private versionNumber:number = 1;
|
private versionNumber: number = 1;
|
||||||
private nextUserId: number = 1;
|
private nextUserId: number = 1;
|
||||||
|
|
||||||
constructor(roomId: string,
|
constructor(
|
||||||
connectCallback: ConnectCallback,
|
roomId: string,
|
||||||
disconnectCallback: DisconnectCallback,
|
connectCallback: ConnectCallback,
|
||||||
minDistance: number,
|
disconnectCallback: DisconnectCallback,
|
||||||
groupRadius: number,
|
minDistance: number,
|
||||||
onEnters: EntersCallback,
|
groupRadius: number,
|
||||||
onMoves: MovesCallback,
|
onEnters: EntersCallback,
|
||||||
onLeaves: LeavesCallback,
|
onMoves: MovesCallback,
|
||||||
onEmote: EmoteCallback,
|
onLeaves: LeavesCallback,
|
||||||
|
onEmote: EmoteCallback
|
||||||
) {
|
) {
|
||||||
this.roomId = roomId;
|
this.roomId = roomId;
|
||||||
|
|
||||||
if (isRoomAnonymous(roomId)) {
|
if (isRoomAnonymous(roomId)) {
|
||||||
this.roomSlug = extractRoomSlugPublicRoomId(this.roomId);
|
this.roomSlug = extractRoomSlugPublicRoomId(this.roomId);
|
||||||
} else {
|
} else {
|
||||||
const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId(this.roomId);
|
const { organizationSlug, worldSlug, roomSlug } = extractDataFromPrivateRoomId(this.roomId);
|
||||||
this.roomSlug = roomSlug;
|
this.roomSlug = roomSlug;
|
||||||
this.organizationSlug = organizationSlug;
|
this.organizationSlug = organizationSlug;
|
||||||
this.worldSlug = worldSlug;
|
this.worldSlug = worldSlug;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.users = new Map<number, User>();
|
this.users = new Map<number, User>();
|
||||||
this.usersByUuid = new Map<string, User>();
|
this.usersByUuid = new Map<string, User>();
|
||||||
this.admins = new Set<Admin>();
|
this.admins = new Set<Admin>();
|
||||||
@ -86,21 +86,22 @@ export class GameRoom {
|
|||||||
return this.users;
|
return this.users;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getUserByUuid(uuid: string): User|undefined {
|
public getUserByUuid(uuid: string): User | undefined {
|
||||||
return this.usersByUuid.get(uuid);
|
return this.usersByUuid.get(uuid);
|
||||||
}
|
}
|
||||||
public getUserById(id: number): User|undefined {
|
public getUserById(id: number): User | undefined {
|
||||||
return this.users.get(id);
|
return this.users.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public join(socket : UserSocket, joinRoomMessage: JoinRoomMessage): User {
|
public join(socket: UserSocket, joinRoomMessage: JoinRoomMessage): User {
|
||||||
const positionMessage = joinRoomMessage.getPositionmessage();
|
const positionMessage = joinRoomMessage.getPositionmessage();
|
||||||
if (positionMessage === undefined) {
|
if (positionMessage === undefined) {
|
||||||
throw new Error('Missing position message');
|
throw new Error("Missing position message");
|
||||||
}
|
}
|
||||||
const position = ProtobufUtils.toPointInterface(positionMessage);
|
const position = ProtobufUtils.toPointInterface(positionMessage);
|
||||||
|
|
||||||
const user = new User(this.nextUserId,
|
const user = new User(
|
||||||
|
this.nextUserId,
|
||||||
joinRoomMessage.getUseruuid(),
|
joinRoomMessage.getUseruuid(),
|
||||||
joinRoomMessage.getIpaddress(),
|
joinRoomMessage.getIpaddress(),
|
||||||
position,
|
position,
|
||||||
@ -126,12 +127,12 @@ export class GameRoom {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public leave(user : User){
|
public leave(user: User) {
|
||||||
const userObj = this.users.get(user.id);
|
const userObj = this.users.get(user.id);
|
||||||
if (userObj === undefined) {
|
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.leaveGroup(userObj);
|
||||||
}
|
}
|
||||||
this.users.delete(user.id);
|
this.users.delete(user.id);
|
||||||
@ -143,7 +144,7 @@ export class GameRoom {
|
|||||||
|
|
||||||
// Notify admins
|
// Notify admins
|
||||||
for (const admin of this.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;
|
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);
|
user.setPosition(userPosition);
|
||||||
|
|
||||||
this.updateUserGroup(user);
|
this.updateUserGroup(user);
|
||||||
@ -173,22 +174,24 @@ export class GameRoom {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const closestItem: User|Group|null = this.searchClosestAvailableUserOrGroup(user);
|
const closestItem: User | Group | null = this.searchClosestAvailableUserOrGroup(user);
|
||||||
|
|
||||||
if (closestItem !== null) {
|
if (closestItem !== null) {
|
||||||
if (closestItem instanceof Group) {
|
if (closestItem instanceof Group) {
|
||||||
// Let's join the group!
|
// Let's join the group!
|
||||||
closestItem.join(user);
|
closestItem.join(user);
|
||||||
} else {
|
} else {
|
||||||
const closestUser : User = closestItem;
|
const closestUser: User = closestItem;
|
||||||
const group: Group = new Group(this.roomId,[
|
const group: Group = new Group(
|
||||||
user,
|
this.roomId,
|
||||||
closestUser
|
[user, closestUser],
|
||||||
], this.connectCallback, this.disconnectCallback, this.positionNotifier);
|
this.connectCallback,
|
||||||
|
this.disconnectCallback,
|
||||||
|
this.positionNotifier
|
||||||
|
);
|
||||||
this.groups.add(group);
|
this.groups.add(group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// If the user is part of a group:
|
// If the user is part of a group:
|
||||||
// should he leave the group?
|
// should he leave the group?
|
||||||
@ -229,7 +232,9 @@ export class GameRoom {
|
|||||||
this.positionNotifier.leave(group);
|
this.positionNotifier.leave(group);
|
||||||
group.destroy();
|
group.destroy();
|
||||||
if (!this.groups.has(group)) {
|
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);
|
this.groups.delete(group);
|
||||||
//todo: is the group garbage collected?
|
//todo: is the group garbage collected?
|
||||||
@ -247,16 +252,15 @@ export class GameRoom {
|
|||||||
* OR
|
* OR
|
||||||
* - close enough to a group (distance <= groupRadius)
|
* - 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 minimumDistanceFound: number = Math.max(this.minDistance, this.groupRadius);
|
||||||
let matchingItem: User | Group | null = null;
|
let matchingItem: User | Group | null = null;
|
||||||
this.users.forEach((currentUser, userId) => {
|
this.users.forEach((currentUser, userId) => {
|
||||||
// Let's only check users that are not part of a group
|
// Let's only check users that are not part of a group
|
||||||
if (typeof currentUser.group !== 'undefined') {
|
if (typeof currentUser.group !== "undefined") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(currentUser === user) {
|
if (currentUser === user) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (currentUser.silent) {
|
if (currentUser.silent) {
|
||||||
@ -265,7 +269,7 @@ export class GameRoom {
|
|||||||
|
|
||||||
const distance = GameRoom.computeDistance(user, currentUser); // compute distance between peers.
|
const distance = GameRoom.computeDistance(user, currentUser); // compute distance between peers.
|
||||||
|
|
||||||
if(distance <= minimumDistanceFound && distance <= this.minDistance) {
|
if (distance <= minimumDistanceFound && distance <= this.minDistance) {
|
||||||
minimumDistanceFound = distance;
|
minimumDistanceFound = distance;
|
||||||
matchingItem = currentUser;
|
matchingItem = currentUser;
|
||||||
}
|
}
|
||||||
@ -276,7 +280,7 @@ export class GameRoom {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), group.getPosition());
|
const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), group.getPosition());
|
||||||
if(distance <= minimumDistanceFound && distance <= this.groupRadius) {
|
if (distance <= minimumDistanceFound && distance <= this.groupRadius) {
|
||||||
minimumDistanceFound = distance;
|
minimumDistanceFound = distance;
|
||||||
matchingItem = group;
|
matchingItem = group;
|
||||||
}
|
}
|
||||||
@ -285,15 +289,15 @@ export class GameRoom {
|
|||||||
return matchingItem;
|
return matchingItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static computeDistance(user1: User, user2: User): number
|
public static computeDistance(user1: User, user2: User): number {
|
||||||
{
|
|
||||||
const user1Position = user1.getPosition();
|
const user1Position = user1.getPosition();
|
||||||
const user2Position = user2.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));
|
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 {
|
public adminLeave(admin: Admin): void {
|
||||||
this.admins.delete(admin);
|
this.admins.delete(admin);
|
||||||
}
|
}
|
||||||
|
|
||||||
public incrementVersion(): number {
|
public incrementVersion(): number {
|
||||||
this.versionNumber++
|
this.versionNumber++;
|
||||||
return this.versionNumber;
|
return this.versionNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import { ConnectCallback, DisconnectCallback } from "./GameRoom";
|
import { ConnectCallback, DisconnectCallback } from "./GameRoom";
|
||||||
import { User } from "./User";
|
import { User } from "./User";
|
||||||
import {PositionInterface} from "_Model/PositionInterface";
|
import { PositionInterface } from "_Model/PositionInterface";
|
||||||
import {Movable} from "_Model/Movable";
|
import { Movable } from "_Model/Movable";
|
||||||
import {PositionNotifier} from "_Model/PositionNotifier";
|
import { PositionNotifier } from "_Model/PositionNotifier";
|
||||||
import {gaugeManager} from "../Services/GaugeManager";
|
import { gaugeManager } from "../Services/GaugeManager";
|
||||||
import {MAX_PER_GROUP} from "../Enum/EnvironmentVariable";
|
import { MAX_PER_GROUP } from "../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
export class Group implements Movable {
|
export class Group implements Movable {
|
||||||
|
|
||||||
private static nextId: number = 1;
|
private static nextId: number = 1;
|
||||||
|
|
||||||
private id: number;
|
private id: number;
|
||||||
@ -18,8 +17,13 @@ export class Group implements Movable {
|
|||||||
private wasDestroyed: boolean = false;
|
private wasDestroyed: boolean = false;
|
||||||
private roomId: string;
|
private roomId: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
constructor(roomId: string, users: User[], private connectCallback: ConnectCallback, private disconnectCallback: DisconnectCallback, private positionNotifier: PositionNotifier) {
|
roomId: string,
|
||||||
|
users: User[],
|
||||||
|
private connectCallback: ConnectCallback,
|
||||||
|
private disconnectCallback: DisconnectCallback,
|
||||||
|
private positionNotifier: PositionNotifier
|
||||||
|
) {
|
||||||
this.roomId = roomId;
|
this.roomId = roomId;
|
||||||
this.users = new Set<User>();
|
this.users = new Set<User>();
|
||||||
this.id = Group.nextId;
|
this.id = Group.nextId;
|
||||||
@ -43,7 +47,7 @@ export class Group implements Movable {
|
|||||||
return Array.from(this.users.values());
|
return Array.from(this.users.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
getId() : number {
|
getId(): number {
|
||||||
return this.id;
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +57,7 @@ export class Group implements Movable {
|
|||||||
getPosition(): PositionInterface {
|
getPosition(): PositionInterface {
|
||||||
return {
|
return {
|
||||||
x: this.x,
|
x: this.x,
|
||||||
y: this.y
|
y: this.y,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +87,7 @@ export class Group implements Movable {
|
|||||||
if (oldX === undefined) {
|
if (oldX === undefined) {
|
||||||
this.positionNotifier.enter(this);
|
this.positionNotifier.enter(this);
|
||||||
} else {
|
} 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;
|
return this.users.size <= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
join(user: User): void
|
join(user: User): void {
|
||||||
{
|
|
||||||
// Broadcast on the right event
|
// Broadcast on the right event
|
||||||
this.connectCallback(user, this);
|
this.connectCallback(user, this);
|
||||||
this.users.add(user);
|
this.users.add(user);
|
||||||
user.group = this;
|
user.group = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
leave(user: User): void
|
leave(user: User): void {
|
||||||
{
|
|
||||||
const success = this.users.delete(user);
|
const success = this.users.delete(user);
|
||||||
if (success === false) {
|
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;
|
user.group = undefined;
|
||||||
|
|
||||||
@ -123,8 +125,7 @@ export class Group implements Movable {
|
|||||||
* Let's kick everybody out.
|
* Let's kick everybody out.
|
||||||
* Usually used when there is only one user left.
|
* Usually used when there is only one user left.
|
||||||
*/
|
*/
|
||||||
destroy(): void
|
destroy(): void {
|
||||||
{
|
|
||||||
if (this.hasEditedGauge) gaugeManager.decNbGroupsPerRoomGauge(this.roomId);
|
if (this.hasEditedGauge) gaugeManager.decNbGroupsPerRoomGauge(this.roomId);
|
||||||
for (const user of this.users) {
|
for (const user of this.users) {
|
||||||
this.leave(user);
|
this.leave(user);
|
||||||
@ -132,7 +133,7 @@ export class Group implements Movable {
|
|||||||
this.wasDestroyed = true;
|
this.wasDestroyed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
get getSize(){
|
get getSize() {
|
||||||
return this.users.size;
|
return this.users.size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import {PositionInterface} from "_Model/PositionInterface";
|
import { PositionInterface } from "_Model/PositionInterface";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A physical object that can be placed into a Zone
|
* A physical object that can be placed into a Zone
|
||||||
*/
|
*/
|
||||||
export interface Movable {
|
export interface Movable {
|
||||||
getPosition(): PositionInterface
|
getPosition(): PositionInterface;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export interface PositionInterface {
|
export interface PositionInterface {
|
||||||
x: number,
|
x: number;
|
||||||
y: number
|
y: number;
|
||||||
}
|
}
|
||||||
|
@ -8,12 +8,12 @@
|
|||||||
* The PositionNotifier is important for performance. It allows us to send the position of players only to a restricted
|
* 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.
|
* number of players around the current player.
|
||||||
*/
|
*/
|
||||||
import {EmoteCallback, EntersCallback, LeavesCallback, MovesCallback, Zone} from "./Zone";
|
import { EmoteCallback, EntersCallback, LeavesCallback, MovesCallback, Zone } from "./Zone";
|
||||||
import {Movable} from "_Model/Movable";
|
import { Movable } from "_Model/Movable";
|
||||||
import {PositionInterface} from "_Model/PositionInterface";
|
import { PositionInterface } from "_Model/PositionInterface";
|
||||||
import {ZoneSocket} from "../RoomManager";
|
import { ZoneSocket } from "../RoomManager";
|
||||||
import {User} from "_Model/User";
|
import { User } from "_Model/User";
|
||||||
import {EmoteEventMessage} from "../Messages/generated/messages_pb";
|
import { EmoteEventMessage } from "../Messages/generated/messages_pb";
|
||||||
|
|
||||||
interface ZoneDescriptor {
|
interface ZoneDescriptor {
|
||||||
i: number;
|
i: number;
|
||||||
@ -21,19 +21,24 @@ interface ZoneDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PositionNotifier {
|
export class PositionNotifier {
|
||||||
|
|
||||||
// TODO: we need a way to clean the zones if noone is in the zone and noone listening (to free memory!)
|
// TODO: we need a way to clean the zones if noone is in the zone and noone listening (to free memory!)
|
||||||
|
|
||||||
private zones: Zone[][] = [];
|
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 {
|
private getZoneDescriptorFromCoordinates(x: number, y: number): ZoneDescriptor {
|
||||||
return {
|
return {
|
||||||
i: Math.floor(x / this.zoneWidth),
|
i: Math.floor(x / this.zoneWidth),
|
||||||
j: Math.floor(y / this.zoneHeight),
|
j: Math.floor(y / this.zoneHeight),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public enter(thing: Movable): void {
|
public enter(thing: Movable): void {
|
||||||
@ -100,6 +105,5 @@ export class PositionNotifier {
|
|||||||
const zoneDesc = this.getZoneDescriptorFromCoordinates(user.getPosition().x, user.getPosition().y);
|
const zoneDesc = this.getZoneDescriptorFromCoordinates(user.getPosition().x, user.getPosition().y);
|
||||||
const zone = this.getZone(zoneDesc.i, zoneDesc.j);
|
const zone = this.getZone(zoneDesc.i, zoneDesc.j);
|
||||||
zone.emitEmoteEvent(emoteEventMessage);
|
zone.emitEmoteEvent(emoteEventMessage);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,30 @@
|
|||||||
//helper functions to parse room IDs
|
//helper functions to parse room IDs
|
||||||
|
|
||||||
export const isRoomAnonymous = (roomID: string): boolean => {
|
export const isRoomAnonymous = (roomID: string): boolean => {
|
||||||
if (roomID.startsWith('_/')) {
|
if (roomID.startsWith("_/")) {
|
||||||
return true;
|
return true;
|
||||||
} else if(roomID.startsWith('@/')) {
|
} else if (roomID.startsWith("@/")) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Incorrect room ID: '+roomID);
|
throw new Error("Incorrect room ID: " + roomID);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export const extractRoomSlugPublicRoomId = (roomId: string): string => {
|
export const extractRoomSlugPublicRoomId = (roomId: string): string => {
|
||||||
const idParts = roomId.split('/');
|
const idParts = roomId.split("/");
|
||||||
if (idParts.length < 3) throw new Error('Incorrect roomId: '+roomId);
|
if (idParts.length < 3) throw new Error("Incorrect roomId: " + roomId);
|
||||||
return idParts.slice(2).join('/');
|
return idParts.slice(2).join("/");
|
||||||
}
|
};
|
||||||
export interface extractDataFromPrivateRoomIdResponse {
|
export interface extractDataFromPrivateRoomIdResponse {
|
||||||
organizationSlug: string;
|
organizationSlug: string;
|
||||||
worldSlug: string;
|
worldSlug: string;
|
||||||
roomSlug: string;
|
roomSlug: string;
|
||||||
}
|
}
|
||||||
export const extractDataFromPrivateRoomId = (roomId: string): extractDataFromPrivateRoomIdResponse => {
|
export const extractDataFromPrivateRoomId = (roomId: string): extractDataFromPrivateRoomIdResponse => {
|
||||||
const idParts = roomId.split('/');
|
const idParts = roomId.split("/");
|
||||||
if (idParts.length < 4) throw new Error('Incorrect roomId: '+roomId);
|
if (idParts.length < 4) throw new Error("Incorrect roomId: " + roomId);
|
||||||
const organizationSlug = idParts[1];
|
const organizationSlug = idParts[1];
|
||||||
const worldSlug = idParts[2];
|
const worldSlug = idParts[2];
|
||||||
const roomSlug = idParts[3];
|
const roomSlug = idParts[3];
|
||||||
return {organizationSlug, worldSlug, roomSlug}
|
return { organizationSlug, worldSlug, roomSlug };
|
||||||
}
|
};
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
import { Group } from "./Group";
|
import { Group } from "./Group";
|
||||||
import { PointInterface } from "./Websocket/PointInterface";
|
import { PointInterface } from "./Websocket/PointInterface";
|
||||||
import {Zone} from "_Model/Zone";
|
import { Zone } from "_Model/Zone";
|
||||||
import {Movable} from "_Model/Movable";
|
import { Movable } from "_Model/Movable";
|
||||||
import {PositionNotifier} from "_Model/PositionNotifier";
|
import { PositionNotifier } from "_Model/PositionNotifier";
|
||||||
import {ServerDuplexStream} from "grpc";
|
import { ServerDuplexStream } from "grpc";
|
||||||
import {BatchMessage, CompanionMessage, PusherToBackMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb";
|
import {
|
||||||
import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
|
BatchMessage,
|
||||||
|
CompanionMessage,
|
||||||
|
PusherToBackMessage,
|
||||||
|
ServerToClientMessage,
|
||||||
|
SubMessage,
|
||||||
|
} from "../Messages/generated/messages_pb";
|
||||||
|
import { CharacterLayer } from "_Model/Websocket/CharacterLayer";
|
||||||
|
|
||||||
export type UserSocket = ServerDuplexStream<PusherToBackMessage, ServerToClientMessage>;
|
export type UserSocket = ServerDuplexStream<PusherToBackMessage, ServerToClientMessage>;
|
||||||
|
|
||||||
@ -22,7 +28,7 @@ export class User implements Movable {
|
|||||||
private positionNotifier: PositionNotifier,
|
private positionNotifier: PositionNotifier,
|
||||||
public readonly socket: UserSocket,
|
public readonly socket: UserSocket,
|
||||||
public readonly tags: string[],
|
public readonly tags: string[],
|
||||||
public readonly visitCardUrl: string|null,
|
public readonly visitCardUrl: string | null,
|
||||||
public readonly name: string,
|
public readonly name: string,
|
||||||
public readonly characterLayers: CharacterLayer[],
|
public readonly characterLayers: CharacterLayer[],
|
||||||
public readonly companion?: CompanionMessage
|
public readonly companion?: CompanionMessage
|
||||||
@ -42,9 +48,8 @@ export class User implements Movable {
|
|||||||
this.positionNotifier.updatePosition(this, position, oldPosition);
|
this.positionNotifier.updatePosition(this, position, oldPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private batchedMessages: BatchMessage = new BatchMessage();
|
private batchedMessages: BatchMessage = new BatchMessage();
|
||||||
private batchTimeout: NodeJS.Timeout|null = null;
|
private batchTimeout: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
public emitInBatch(payload: SubMessage): void {
|
public emitInBatch(payload: SubMessage): void {
|
||||||
this.batchedMessages.addPayload(payload);
|
this.batchedMessages.addPayload(payload);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export interface CharacterLayer {
|
export interface CharacterLayer {
|
||||||
name: string,
|
name: string;
|
||||||
url: string|undefined
|
url: string | undefined;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
export const isItemEventMessageInterface =
|
export const isItemEventMessageInterface = new tg.IsInterface()
|
||||||
new tg.IsInterface().withProperties({
|
.withProperties({
|
||||||
itemId: tg.isNumber,
|
itemId: tg.isNumber,
|
||||||
event: tg.isString,
|
event: tg.isString,
|
||||||
state: tg.isUnknown,
|
state: tg.isUnknown,
|
||||||
parameters: tg.isUnknown,
|
parameters: tg.isUnknown,
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
export type ItemEventMessageInterface = tg.GuardedType<typeof isItemEventMessageInterface>;
|
export type ItemEventMessageInterface = tg.GuardedType<typeof isItemEventMessageInterface>;
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import {PointInterface} from "./PointInterface";
|
import { PointInterface } from "./PointInterface";
|
||||||
|
|
||||||
export class Point implements PointInterface{
|
export class Point implements PointInterface {
|
||||||
constructor(public x : number, public y : number, public direction : string = "none", public moving : boolean = false) {
|
constructor(
|
||||||
}
|
public x: number,
|
||||||
|
public y: number,
|
||||||
|
public direction: string = "none",
|
||||||
|
public moving: boolean = false
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,11 +7,12 @@ import * as tg from "generic-type-guard";
|
|||||||
readonly moving: boolean;
|
readonly moving: boolean;
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
export const isPointInterface =
|
export const isPointInterface = new tg.IsInterface()
|
||||||
new tg.IsInterface().withProperties({
|
.withProperties({
|
||||||
x: tg.isNumber,
|
x: tg.isNumber,
|
||||||
y: tg.isNumber,
|
y: tg.isNumber,
|
||||||
direction: tg.isString,
|
direction: tg.isString,
|
||||||
moving: tg.isBoolean
|
moving: tg.isBoolean,
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
export type PointInterface = tg.GuardedType<typeof isPointInterface>;
|
export type PointInterface = tg.GuardedType<typeof isPointInterface>;
|
||||||
|
@ -1,34 +1,33 @@
|
|||||||
import {PointInterface} from "./PointInterface";
|
import { PointInterface } from "./PointInterface";
|
||||||
import {
|
import {
|
||||||
CharacterLayerMessage,
|
CharacterLayerMessage,
|
||||||
ItemEventMessage,
|
ItemEventMessage,
|
||||||
PointMessage,
|
PointMessage,
|
||||||
PositionMessage
|
PositionMessage,
|
||||||
} from "../../Messages/generated/messages_pb";
|
} from "../../Messages/generated/messages_pb";
|
||||||
import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
|
import { CharacterLayer } from "_Model/Websocket/CharacterLayer";
|
||||||
import Direction = PositionMessage.Direction;
|
import Direction = PositionMessage.Direction;
|
||||||
import {ItemEventMessageInterface} from "_Model/Websocket/ItemEventMessage";
|
import { ItemEventMessageInterface } from "_Model/Websocket/ItemEventMessage";
|
||||||
import {PositionInterface} from "_Model/PositionInterface";
|
import { PositionInterface } from "_Model/PositionInterface";
|
||||||
|
|
||||||
export class ProtobufUtils {
|
export class ProtobufUtils {
|
||||||
|
|
||||||
public static toPositionMessage(point: PointInterface): PositionMessage {
|
public static toPositionMessage(point: PointInterface): PositionMessage {
|
||||||
let direction: Direction;
|
let direction: Direction;
|
||||||
switch (point.direction) {
|
switch (point.direction) {
|
||||||
case 'up':
|
case "up":
|
||||||
direction = Direction.UP;
|
direction = Direction.UP;
|
||||||
break;
|
break;
|
||||||
case 'down':
|
case "down":
|
||||||
direction = Direction.DOWN;
|
direction = Direction.DOWN;
|
||||||
break;
|
break;
|
||||||
case 'left':
|
case "left":
|
||||||
direction = Direction.LEFT;
|
direction = Direction.LEFT;
|
||||||
break;
|
break;
|
||||||
case 'right':
|
case "right":
|
||||||
direction = Direction.RIGHT;
|
direction = Direction.RIGHT;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error('unexpected direction');
|
throw new Error("unexpected direction");
|
||||||
}
|
}
|
||||||
|
|
||||||
const position = new PositionMessage();
|
const position = new PositionMessage();
|
||||||
@ -44,16 +43,16 @@ export class ProtobufUtils {
|
|||||||
let direction: string;
|
let direction: string;
|
||||||
switch (position.getDirection()) {
|
switch (position.getDirection()) {
|
||||||
case Direction.UP:
|
case Direction.UP:
|
||||||
direction = 'up';
|
direction = "up";
|
||||||
break;
|
break;
|
||||||
case Direction.DOWN:
|
case Direction.DOWN:
|
||||||
direction = 'down';
|
direction = "down";
|
||||||
break;
|
break;
|
||||||
case Direction.LEFT:
|
case Direction.LEFT:
|
||||||
direction = 'left';
|
direction = "left";
|
||||||
break;
|
break;
|
||||||
case Direction.RIGHT:
|
case Direction.RIGHT:
|
||||||
direction = 'right';
|
direction = "right";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error("Unexpected direction");
|
throw new Error("Unexpected direction");
|
||||||
@ -82,7 +81,7 @@ export class ProtobufUtils {
|
|||||||
event: itemEventMessage.getEvent(),
|
event: itemEventMessage.getEvent(),
|
||||||
parameters: JSON.parse(itemEventMessage.getParametersjson()),
|
parameters: JSON.parse(itemEventMessage.getParametersjson()),
|
||||||
state: JSON.parse(itemEventMessage.getStatejson()),
|
state: JSON.parse(itemEventMessage.getStatejson()),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static toItemEventProtobuf(itemEvent: ItemEventMessageInterface): ItemEventMessage {
|
public static toItemEventProtobuf(itemEvent: ItemEventMessageInterface): ItemEventMessage {
|
||||||
@ -96,7 +95,7 @@ export class ProtobufUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static toCharacterLayerMessages(characterLayers: CharacterLayer[]): CharacterLayerMessage[] {
|
public static toCharacterLayerMessages(characterLayers: CharacterLayer[]): CharacterLayerMessage[] {
|
||||||
return characterLayers.map(function(characterLayer): CharacterLayerMessage {
|
return characterLayers.map(function (characterLayer): CharacterLayerMessage {
|
||||||
const message = new CharacterLayerMessage();
|
const message = new CharacterLayerMessage();
|
||||||
message.setName(characterLayer.name);
|
message.setName(characterLayer.name);
|
||||||
if (characterLayer.url) {
|
if (characterLayer.url) {
|
||||||
@ -107,7 +106,7 @@ export class ProtobufUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static toCharacterLayerObjects(characterLayers: CharacterLayerMessage[]): CharacterLayer[] {
|
public static toCharacterLayerObjects(characterLayers: CharacterLayerMessage[]): CharacterLayer[] {
|
||||||
return characterLayers.map(function(characterLayer): CharacterLayer {
|
return characterLayers.map(function (characterLayer): CharacterLayer {
|
||||||
const url = characterLayer.getUrl();
|
const url = characterLayer.getUrl();
|
||||||
return {
|
return {
|
||||||
name: characterLayer.getName(),
|
name: characterLayer.getName(),
|
||||||
|
@ -1,35 +1,52 @@
|
|||||||
import {User} from "./User";
|
import { User } from "./User";
|
||||||
import {PositionInterface} from "_Model/PositionInterface";
|
import { PositionInterface } from "_Model/PositionInterface";
|
||||||
import {Movable} from "./Movable";
|
import { Movable } from "./Movable";
|
||||||
import {Group} from "./Group";
|
import { Group } from "./Group";
|
||||||
import {ZoneSocket} from "../RoomManager";
|
import { ZoneSocket } from "../RoomManager";
|
||||||
import {EmoteEventMessage} from "../Messages/generated/messages_pb";
|
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 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 type EmoteCallback = (emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) => void;
|
||||||
|
|
||||||
export class Zone {
|
export class Zone {
|
||||||
private things: Set<Movable> = new Set<Movable>();
|
private things: Set<Movable> = new Set<Movable>();
|
||||||
private listeners: Set<ZoneSocket> = new Set<ZoneSocket>();
|
private listeners: Set<ZoneSocket> = new Set<ZoneSocket>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
constructor(private onEnters: EntersCallback, private onMoves: MovesCallback, private onLeaves: LeavesCallback, private onEmote: EmoteCallback, public readonly x: number, public readonly y: number) { }
|
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
|
* 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);
|
const result = this.things.delete(thing);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
if (thing instanceof User) {
|
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) {
|
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);
|
this.notifyLeft(thing, newZone);
|
||||||
}
|
}
|
||||||
@ -37,13 +54,13 @@ export class Zone {
|
|||||||
/**
|
/**
|
||||||
* Notify listeners of this zone that this user/thing left
|
* 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) {
|
for (const listener of this.listeners) {
|
||||||
this.onLeaves(thing, newZone, listener);
|
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.things.add(thing);
|
||||||
this.notifyEnter(thing, oldZone, position);
|
this.notifyEnter(thing, oldZone, position);
|
||||||
}
|
}
|
||||||
@ -51,13 +68,12 @@ export class Zone {
|
|||||||
/**
|
/**
|
||||||
* Notify listeners of this zone that this user entered
|
* 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) {
|
for (const listener of this.listeners) {
|
||||||
this.onEnters(thing, oldZone, listener);
|
this.onEnters(thing, oldZone, listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public move(thing: Movable, position: PositionInterface) {
|
public move(thing: Movable, position: PositionInterface) {
|
||||||
if (!this.things.has(thing)) {
|
if (!this.things.has(thing)) {
|
||||||
this.things.add(thing);
|
this.things.add(thing);
|
||||||
@ -67,7 +83,7 @@ export class Zone {
|
|||||||
|
|
||||||
for (const listener of this.listeners) {
|
for (const listener of this.listeners) {
|
||||||
//if (listener !== thing) {
|
//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) {
|
for (const listener of this.listeners) {
|
||||||
this.onEmote(emoteEventMessage, listener);
|
this.onEmote(emoteEventMessage, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {IRoomManagerServer} from "./Messages/generated/messages_grpc_pb";
|
import { IRoomManagerServer } from "./Messages/generated/messages_grpc_pb";
|
||||||
import {
|
import {
|
||||||
AdminGlobalMessage,
|
AdminGlobalMessage,
|
||||||
AdminMessage,
|
AdminMessage,
|
||||||
@ -11,92 +11,114 @@ import {
|
|||||||
JoinRoomMessage,
|
JoinRoomMessage,
|
||||||
PlayGlobalMessage,
|
PlayGlobalMessage,
|
||||||
PusherToBackMessage,
|
PusherToBackMessage,
|
||||||
QueryJitsiJwtMessage, RefreshRoomPromptMessage,
|
QueryJitsiJwtMessage,
|
||||||
|
RefreshRoomPromptMessage,
|
||||||
ServerToAdminClientMessage,
|
ServerToAdminClientMessage,
|
||||||
ServerToClientMessage,
|
ServerToClientMessage,
|
||||||
SilentMessage,
|
SilentMessage,
|
||||||
UserMovesMessage,
|
UserMovesMessage,
|
||||||
WebRtcSignalToServerMessage, WorldFullWarningToRoomMessage,
|
WebRtcSignalToServerMessage,
|
||||||
ZoneMessage
|
WorldFullWarningToRoomMessage,
|
||||||
|
ZoneMessage,
|
||||||
} from "./Messages/generated/messages_pb";
|
} from "./Messages/generated/messages_pb";
|
||||||
import {sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream} from "grpc";
|
import { sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream } from "grpc";
|
||||||
import {socketManager} from "./Services/SocketManager";
|
import { socketManager } from "./Services/SocketManager";
|
||||||
import {emitError} from "./Services/MessageHelpers";
|
import { emitError } from "./Services/MessageHelpers";
|
||||||
import {User, UserSocket} from "./Model/User";
|
import { User, UserSocket } from "./Model/User";
|
||||||
import {GameRoom} from "./Model/GameRoom";
|
import { GameRoom } from "./Model/GameRoom";
|
||||||
import Debug from "debug";
|
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<AdminPusherToBackMessage, ServerToAdminClientMessage>;
|
export type AdminSocket = ServerDuplexStream<AdminPusherToBackMessage, ServerToAdminClientMessage>;
|
||||||
export type ZoneSocket = ServerWritableStream<ZoneMessage, ServerToClientMessage>;
|
export type ZoneSocket = ServerWritableStream<ZoneMessage, ServerToClientMessage>;
|
||||||
|
|
||||||
const roomManager: IRoomManagerServer = {
|
const roomManager: IRoomManagerServer = {
|
||||||
joinRoom: (call: UserSocket): void => {
|
joinRoom: (call: UserSocket): void => {
|
||||||
console.log('joinRoom called');
|
console.log("joinRoom called");
|
||||||
|
|
||||||
let room: GameRoom|null = null;
|
let room: GameRoom | null = null;
|
||||||
let user: User|null = null;
|
let user: User | null = null;
|
||||||
|
|
||||||
call.on('data', (message: PusherToBackMessage) => {
|
call.on("data", (message: PusherToBackMessage) => {
|
||||||
try {
|
try {
|
||||||
if (room === null || user === null) {
|
if (room === null || user === null) {
|
||||||
if (message.hasJoinroommessage()) {
|
if (message.hasJoinroommessage()) {
|
||||||
socketManager.handleJoinRoom(call, message.getJoinroommessage() as JoinRoomMessage).then(({room: gameRoom, user: myUser}) => {
|
socketManager
|
||||||
if (call.writable) {
|
.handleJoinRoom(call, message.getJoinroommessage() as JoinRoomMessage)
|
||||||
room = gameRoom;
|
.then(({ room: gameRoom, user: myUser }) => {
|
||||||
user = myUser;
|
if (call.writable) {
|
||||||
} else {
|
room = gameRoom;
|
||||||
//Connexion may have been closed before the init was finished, so we have to manually disconnect the user.
|
user = myUser;
|
||||||
socketManager.leaveRoom(gameRoom, 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 {
|
} 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 {
|
} else {
|
||||||
if (message.hasJoinroommessage()) {
|
if (message.hasJoinroommessage()) {
|
||||||
throw new Error('Cannot call JoinRoomMessage twice!');
|
throw new Error("Cannot call JoinRoomMessage twice!");
|
||||||
} else if (message.hasUsermovesmessage()) {
|
} else if (message.hasUsermovesmessage()) {
|
||||||
socketManager.handleUserMovesMessage(room, user, message.getUsermovesmessage() as UserMovesMessage);
|
socketManager.handleUserMovesMessage(
|
||||||
|
room,
|
||||||
|
user,
|
||||||
|
message.getUsermovesmessage() as UserMovesMessage
|
||||||
|
);
|
||||||
} else if (message.hasSilentmessage()) {
|
} else if (message.hasSilentmessage()) {
|
||||||
socketManager.handleSilentMessage(room, user, message.getSilentmessage() as SilentMessage);
|
socketManager.handleSilentMessage(room, user, message.getSilentmessage() as SilentMessage);
|
||||||
} else if (message.hasItemeventmessage()) {
|
} else if (message.hasItemeventmessage()) {
|
||||||
socketManager.handleItemEvent(room, user, message.getItemeventmessage() as ItemEventMessage);
|
socketManager.handleItemEvent(room, user, message.getItemeventmessage() as ItemEventMessage);
|
||||||
} else if (message.hasWebrtcsignaltoservermessage()) {
|
} else if (message.hasWebrtcsignaltoservermessage()) {
|
||||||
socketManager.emitVideo(room, user, message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage);
|
socketManager.emitVideo(
|
||||||
|
room,
|
||||||
|
user,
|
||||||
|
message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage
|
||||||
|
);
|
||||||
} else if (message.hasWebrtcscreensharingsignaltoservermessage()) {
|
} else if (message.hasWebrtcscreensharingsignaltoservermessage()) {
|
||||||
socketManager.emitScreenSharing(room, user, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage);
|
socketManager.emitScreenSharing(
|
||||||
|
room,
|
||||||
|
user,
|
||||||
|
message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage
|
||||||
|
);
|
||||||
} else if (message.hasPlayglobalmessage()) {
|
} else if (message.hasPlayglobalmessage()) {
|
||||||
socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage);
|
socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage);
|
||||||
} else if (message.hasQueryjitsijwtmessage()){
|
} else if (message.hasQueryjitsijwtmessage()) {
|
||||||
socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
|
socketManager.handleQueryJitsiJwtMessage(
|
||||||
} else if (message.hasEmotepromptmessage()){
|
user,
|
||||||
socketManager.handleEmoteEventMessage(room, user, message.getEmotepromptmessage() as EmotePromptMessage);
|
message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage
|
||||||
}else if (message.hasSendusermessage()) {
|
);
|
||||||
|
} else if (message.hasEmotepromptmessage()) {
|
||||||
|
socketManager.handleEmoteEventMessage(
|
||||||
|
room,
|
||||||
|
user,
|
||||||
|
message.getEmotepromptmessage() as EmotePromptMessage
|
||||||
|
);
|
||||||
|
} else if (message.hasSendusermessage()) {
|
||||||
const sendUserMessage = message.getSendusermessage();
|
const sendUserMessage = message.getSendusermessage();
|
||||||
if(sendUserMessage !== undefined) {
|
if (sendUserMessage !== undefined) {
|
||||||
socketManager.handlerSendUserMessage(user, sendUserMessage);
|
socketManager.handlerSendUserMessage(user, sendUserMessage);
|
||||||
}
|
}
|
||||||
}else if (message.hasBanusermessage()) {
|
} else if (message.hasBanusermessage()) {
|
||||||
const banUserMessage = message.getBanusermessage();
|
const banUserMessage = message.getBanusermessage();
|
||||||
if(banUserMessage !== undefined) {
|
if (banUserMessage !== undefined) {
|
||||||
socketManager.handlerBanUserMessage(room, user, banUserMessage);
|
socketManager.handlerBanUserMessage(room, user, banUserMessage);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unhandled message type');
|
throw new Error("Unhandled message type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emitError(call, e);
|
emitError(call, e);
|
||||||
call.end();
|
call.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
call.on('end', () => {
|
call.on("end", () => {
|
||||||
debug('joinRoom ended');
|
debug("joinRoom ended");
|
||||||
if (user !== null && room !== null) {
|
if (user !== null && room !== null) {
|
||||||
socketManager.leaveRoom(room, user);
|
socketManager.leaveRoom(room, user);
|
||||||
}
|
}
|
||||||
@ -105,41 +127,40 @@ const roomManager: IRoomManagerServer = {
|
|||||||
user = null;
|
user = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
call.on('error', (err: Error) => {
|
call.on("error", (err: Error) => {
|
||||||
console.error('An error occurred in joinRoom stream:', err);
|
console.error("An error occurred in joinRoom stream:", err);
|
||||||
});
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
listenZone(call: ZoneSocket): void {
|
listenZone(call: ZoneSocket): void {
|
||||||
debug('listenZone called');
|
debug("listenZone called");
|
||||||
const zoneMessage = call.request;
|
const zoneMessage = call.request;
|
||||||
|
|
||||||
socketManager.addZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
|
socketManager.addZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
|
||||||
|
|
||||||
call.on('cancelled', () => {
|
call.on("cancelled", () => {
|
||||||
debug('listenZone cancelled');
|
debug("listenZone cancelled");
|
||||||
socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
|
socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
|
||||||
call.end();
|
call.end();
|
||||||
})
|
});
|
||||||
|
|
||||||
call.on('close', () => {
|
call.on("close", () => {
|
||||||
debug('listenZone connection closed');
|
debug("listenZone connection closed");
|
||||||
socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
|
socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
|
||||||
}).on('error', (e) => {
|
}).on("error", (e) => {
|
||||||
console.error('An error occurred in listenZone stream:', e);
|
console.error("An error occurred in listenZone stream:", e);
|
||||||
socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
|
socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
|
||||||
call.end();
|
call.end();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
adminRoom(call: AdminSocket): void {
|
adminRoom(call: AdminSocket): void {
|
||||||
console.log('adminRoom called');
|
console.log("adminRoom called");
|
||||||
|
|
||||||
const admin = new Admin(call);
|
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 {
|
try {
|
||||||
if (room === null) {
|
if (room === null) {
|
||||||
if (message.hasSubscribetoroom()) {
|
if (message.hasSubscribetoroom()) {
|
||||||
@ -148,18 +169,17 @@ const roomManager: IRoomManagerServer = {
|
|||||||
room = gameRoom;
|
room = gameRoom;
|
||||||
});
|
});
|
||||||
} else {
|
} 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) {
|
} catch (e) {
|
||||||
emitError(call, e);
|
emitError(call, e);
|
||||||
call.end();
|
call.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
call.on('end', () => {
|
call.on("end", () => {
|
||||||
debug('joinRoom ended');
|
debug("joinRoom ended");
|
||||||
if (room !== null) {
|
if (room !== null) {
|
||||||
socketManager.leaveAdminRoom(room, admin);
|
socketManager.leaveAdminRoom(room, admin);
|
||||||
}
|
}
|
||||||
@ -167,18 +187,21 @@ const roomManager: IRoomManagerServer = {
|
|||||||
room = null;
|
room = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
call.on('error', (err: Error) => {
|
call.on("error", (err: Error) => {
|
||||||
console.error('An error occurred in joinAdminRoom stream:', err);
|
console.error("An error occurred in joinAdminRoom stream:", err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
sendAdminMessage(call: ServerUnaryCall<AdminMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
sendAdminMessage(call: ServerUnaryCall<AdminMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
||||||
|
socketManager.sendAdminMessage(
|
||||||
socketManager.sendAdminMessage(call.request.getRoomid(), call.request.getRecipientuuid(), call.request.getMessage());
|
call.request.getRoomid(),
|
||||||
|
call.request.getRecipientuuid(),
|
||||||
|
call.request.getMessage()
|
||||||
|
);
|
||||||
|
|
||||||
callback(null, new EmptyMessage());
|
callback(null, new EmptyMessage());
|
||||||
},
|
},
|
||||||
sendGlobalAdminMessage(call: ServerUnaryCall<AdminGlobalMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
sendGlobalAdminMessage(call: ServerUnaryCall<AdminGlobalMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
||||||
throw new Error('Not implemented yet');
|
throw new Error("Not implemented yet");
|
||||||
// TODO
|
// TODO
|
||||||
callback(null, new EmptyMessage());
|
callback(null, new EmptyMessage());
|
||||||
},
|
},
|
||||||
@ -192,14 +215,20 @@ const roomManager: IRoomManagerServer = {
|
|||||||
socketManager.sendAdminRoomMessage(call.request.getRoomid(), call.request.getMessage());
|
socketManager.sendAdminRoomMessage(call.request.getRoomid(), call.request.getMessage());
|
||||||
callback(null, new EmptyMessage());
|
callback(null, new EmptyMessage());
|
||||||
},
|
},
|
||||||
sendWorldFullWarningToRoom(call: ServerUnaryCall<WorldFullWarningToRoomMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
sendWorldFullWarningToRoom(
|
||||||
|
call: ServerUnaryCall<WorldFullWarningToRoomMessage>,
|
||||||
|
callback: sendUnaryData<EmptyMessage>
|
||||||
|
): void {
|
||||||
socketManager.dispatchWorlFullWarning(call.request.getRoomid());
|
socketManager.dispatchWorlFullWarning(call.request.getRoomid());
|
||||||
callback(null, new EmptyMessage());
|
callback(null, new EmptyMessage());
|
||||||
},
|
},
|
||||||
sendRefreshRoomPrompt(call: ServerUnaryCall<RefreshRoomPromptMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
sendRefreshRoomPrompt(
|
||||||
|
call: ServerUnaryCall<RefreshRoomPromptMessage>,
|
||||||
|
callback: sendUnaryData<EmptyMessage>
|
||||||
|
): void {
|
||||||
socketManager.dispatchRoomRefresh(call.request.getRoomid());
|
socketManager.dispatchRoomRefresh(call.request.getRoomid());
|
||||||
callback(null, new EmptyMessage());
|
callback(null, new EmptyMessage());
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export {roomManager};
|
export { roomManager };
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { App as _App, AppOptions } from 'uWebSockets.js';
|
import { App as _App, AppOptions } from "uWebSockets.js";
|
||||||
import BaseApp from './baseapp';
|
import BaseApp from "./baseapp";
|
||||||
import { extend } from './utils';
|
import { extend } from "./utils";
|
||||||
import { UwsApp } from './types';
|
import { UwsApp } from "./types";
|
||||||
|
|
||||||
class App extends (<UwsApp>_App) {
|
class App extends (<UwsApp>_App) {
|
||||||
constructor(options: AppOptions = {}) {
|
constructor(options: AppOptions = {}) {
|
||||||
super(options); // eslint-disable-line constructor-super
|
super(options); // eslint-disable-line constructor-super
|
||||||
extend(this, new BaseApp());
|
extend(this, new BaseApp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -1,116 +1,109 @@
|
|||||||
import { Readable } from 'stream';
|
import { Readable } from "stream";
|
||||||
import { us_listen_socket_close, TemplatedApp, HttpResponse, HttpRequest } from 'uWebSockets.js';
|
import { us_listen_socket_close, TemplatedApp, HttpResponse, HttpRequest } from "uWebSockets.js";
|
||||||
|
|
||||||
import formData from './formdata';
|
import formData from "./formdata";
|
||||||
import { stob } from './utils';
|
import { stob } from "./utils";
|
||||||
import { Handler } from './types';
|
import { Handler } from "./types";
|
||||||
import {join} from "path";
|
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 noOp = () => true;
|
||||||
|
|
||||||
const handleBody = (res: HttpResponse, req: HttpRequest) => {
|
const handleBody = (res: HttpResponse, req: HttpRequest) => {
|
||||||
const contType = req.getHeader('content-type');
|
const contType = req.getHeader("content-type");
|
||||||
|
|
||||||
res.bodyStream = function() {
|
res.bodyStream = function () {
|
||||||
const stream = new Readable();
|
const stream = new Readable();
|
||||||
stream._read = noOp; // eslint-disable-line @typescript-eslint/unbound-method
|
stream._read = noOp; // eslint-disable-line @typescript-eslint/unbound-method
|
||||||
|
|
||||||
this.onData((ab: ArrayBuffer, isLast: boolean) => {
|
this.onData((ab: ArrayBuffer, isLast: boolean) => {
|
||||||
// uint and then slicing is bit faster than slice and then uint
|
// 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
|
stream.push(new Uint8Array(ab.slice((ab as any).byteOffset, ab.byteLength))); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
if (isLast) {
|
if (isLast) {
|
||||||
stream.push(null);
|
stream.push(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return stream;
|
return stream;
|
||||||
};
|
};
|
||||||
|
|
||||||
res.body = () => stob(res.bodyStream());
|
res.body = () => stob(res.bodyStream());
|
||||||
|
|
||||||
if (contType.includes('application/json'))
|
if (contType.includes("application/json")) res.json = async () => JSON.parse(await res.body());
|
||||||
res.json = async () => JSON.parse(await res.body());
|
if (contTypes.map((t) => contType.includes(t)).includes(true)) res.formData = formData.bind(res, contType);
|
||||||
if (contTypes.map(t => contType.includes(t)).includes(true))
|
|
||||||
res.formData = formData.bind(res, contType);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class BaseApp {
|
class BaseApp {
|
||||||
_sockets = new Map();
|
_sockets = new Map();
|
||||||
ws!: TemplatedApp['ws'];
|
ws!: TemplatedApp["ws"];
|
||||||
get!: TemplatedApp['get'];
|
get!: TemplatedApp["get"];
|
||||||
_post!: TemplatedApp['post'];
|
_post!: TemplatedApp["post"];
|
||||||
_put!: TemplatedApp['put'];
|
_put!: TemplatedApp["put"];
|
||||||
_patch!: TemplatedApp['patch'];
|
_patch!: TemplatedApp["patch"];
|
||||||
_listen!: TemplatedApp['listen'];
|
_listen!: TemplatedApp["listen"];
|
||||||
|
|
||||||
post(pattern: string, handler: Handler) {
|
post(pattern: string, handler: Handler) {
|
||||||
if (typeof handler !== 'function')
|
if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`);
|
||||||
throw Error(`handler should be a function, given ${typeof handler}.`);
|
this._post(pattern, (res, req) => {
|
||||||
this._post(pattern, (res, req) => {
|
handleBody(res, req);
|
||||||
handleBody(res, req);
|
handler(res, req);
|
||||||
handler(res, req);
|
});
|
||||||
});
|
return this;
|
||||||
return this;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
put(pattern: string, handler: Handler) {
|
put(pattern: string, handler: Handler) {
|
||||||
if (typeof handler !== 'function')
|
if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`);
|
||||||
throw Error(`handler should be a function, given ${typeof handler}.`);
|
this._put(pattern, (res, req) => {
|
||||||
this._put(pattern, (res, req) => {
|
handleBody(res, req);
|
||||||
handleBody(res, req);
|
|
||||||
|
|
||||||
handler(res, req);
|
handler(res, req);
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
patch(pattern: string, handler: Handler) {
|
patch(pattern: string, handler: Handler) {
|
||||||
if (typeof handler !== 'function')
|
if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`);
|
||||||
throw Error(`handler should be a function, given ${typeof handler}.`);
|
this._patch(pattern, (res, req) => {
|
||||||
this._patch(pattern, (res, req) => {
|
handleBody(res, req);
|
||||||
handleBody(res, req);
|
|
||||||
|
|
||||||
handler(res, req);
|
handler(res, req);
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
listen(h: string | number, p: Function | number = noOp, cb?: Function) {
|
listen(h: string | number, p: Function | number = noOp, cb?: Function) {
|
||||||
if (typeof p === 'number' && typeof h === 'string') {
|
if (typeof p === "number" && typeof h === "string") {
|
||||||
this._listen(h, p, socket => {
|
this._listen(h, p, (socket) => {
|
||||||
this._sockets.set(p, socket);
|
this._sockets.set(p, socket);
|
||||||
if (cb === undefined) {
|
if (cb === undefined) {
|
||||||
throw new Error('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);
|
|
||||||
});
|
return this;
|
||||||
} 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;
|
close(port: null | number = null) {
|
||||||
}
|
if (port) {
|
||||||
|
this._sockets.has(port) && us_listen_socket_close(this._sockets.get(port));
|
||||||
close(port: null | number = null) {
|
this._sockets.delete(port);
|
||||||
if (port) {
|
} else {
|
||||||
this._sockets.has(port) && us_listen_socket_close(this._sockets.get(port));
|
this._sockets.forEach((app) => {
|
||||||
this._sockets.delete(port);
|
us_listen_socket_close(app);
|
||||||
} else {
|
});
|
||||||
this._sockets.forEach(app => {
|
this._sockets.clear();
|
||||||
us_listen_socket_close(app);
|
}
|
||||||
});
|
return this;
|
||||||
this._sockets.clear();
|
|
||||||
}
|
}
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BaseApp;
|
export default BaseApp;
|
||||||
|
@ -1,100 +1,99 @@
|
|||||||
import { createWriteStream } from 'fs';
|
import { createWriteStream } from "fs";
|
||||||
import { join, dirname } from 'path';
|
import { join, dirname } from "path";
|
||||||
import Busboy from 'busboy';
|
import Busboy from "busboy";
|
||||||
import mkdirp from 'mkdirp';
|
import mkdirp from "mkdirp";
|
||||||
|
|
||||||
function formData(
|
function formData(
|
||||||
contType: string,
|
contType: string,
|
||||||
options: busboy.BusboyConfig & {
|
options: busboy.BusboyConfig & {
|
||||||
abortOnLimit?: boolean;
|
abortOnLimit?: boolean;
|
||||||
tmpDir?: string;
|
tmpDir?: string;
|
||||||
onFile?: (
|
onFile?: (
|
||||||
fieldname: string,
|
fieldname: string,
|
||||||
file: NodeJS.ReadableStream,
|
file: NodeJS.ReadableStream,
|
||||||
filename: string,
|
filename: string,
|
||||||
encoding: string,
|
encoding: string,
|
||||||
mimetype: string
|
mimetype: string
|
||||||
) => string;
|
) => string;
|
||||||
onField?: (fieldname: string, value: any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any
|
onField?: (fieldname: string, value: any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
filename?: (oldName: string) => string;
|
filename?: (oldName: string) => string;
|
||||||
} = {}
|
} = {}
|
||||||
) {
|
) {
|
||||||
console.log('Enter form data');
|
console.log("Enter form data");
|
||||||
options.headers = {
|
options.headers = {
|
||||||
'content-type': contType
|
"content-type": contType,
|
||||||
};
|
};
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const busb = new Busboy(options);
|
const busb = new Busboy(options);
|
||||||
const ret = {};
|
const ret = {};
|
||||||
|
|
||||||
this.bodyStream().pipe(busb);
|
this.bodyStream().pipe(busb);
|
||||||
|
|
||||||
busb.on('limit', () => {
|
busb.on("limit", () => {
|
||||||
if (options.abortOnLimit) {
|
if (options.abortOnLimit) {
|
||||||
reject(Error('limit'));
|
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(
|
function setRetValue(
|
||||||
ret: { [x: string]: any }, // eslint-disable-line @typescript-eslint/no-explicit-any
|
ret: { [x: string]: any }, // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
fieldname: string,
|
fieldname: string,
|
||||||
value: { filename: string; encoding: string; mimetype: string; filePath?: string } | any // eslint-disable-line @typescript-eslint/no-explicit-any
|
value: { filename: string; encoding: string; mimetype: string; filePath?: string } | any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
) {
|
) {
|
||||||
if (fieldname.endsWith('[]')) {
|
if (fieldname.endsWith("[]")) {
|
||||||
fieldname = fieldname.slice(0, fieldname.length - 2);
|
fieldname = fieldname.slice(0, fieldname.length - 2);
|
||||||
if (Array.isArray(ret[fieldname])) {
|
if (Array.isArray(ret[fieldname])) {
|
||||||
ret[fieldname].push(value);
|
ret[fieldname].push(value);
|
||||||
|
} else {
|
||||||
|
ret[fieldname] = [value];
|
||||||
|
}
|
||||||
} else {
|
} 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;
|
export default formData;
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { SSLApp as _SSLApp, AppOptions } from 'uWebSockets.js';
|
import { SSLApp as _SSLApp, AppOptions } from "uWebSockets.js";
|
||||||
import BaseApp from './baseapp';
|
import BaseApp from "./baseapp";
|
||||||
import { extend } from './utils';
|
import { extend } from "./utils";
|
||||||
import { UwsApp } from './types';
|
import { UwsApp } from "./types";
|
||||||
|
|
||||||
class SSLApp extends (<UwsApp>_SSLApp) {
|
class SSLApp extends (<UwsApp>_SSLApp) {
|
||||||
constructor(options: AppOptions) {
|
constructor(options: AppOptions) {
|
||||||
super(options); // eslint-disable-line constructor-super
|
super(options); // eslint-disable-line constructor-super
|
||||||
extend(this, new BaseApp());
|
extend(this, new BaseApp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SSLApp;
|
export default SSLApp;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { AppOptions, TemplatedApp, HttpResponse, HttpRequest } from 'uWebSockets.js';
|
import { AppOptions, TemplatedApp, HttpResponse, HttpRequest } from "uWebSockets.js";
|
||||||
|
|
||||||
export type UwsApp = {
|
export type UwsApp = {
|
||||||
(options: AppOptions): TemplatedApp;
|
(options: AppOptions): TemplatedApp;
|
||||||
new (options: AppOptions): TemplatedApp;
|
new (options: AppOptions): TemplatedApp;
|
||||||
prototype: TemplatedApp;
|
prototype: TemplatedApp;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Handler = (res: HttpResponse, req: HttpRequest) => void;
|
export type Handler = (res: HttpResponse, req: HttpRequest) => void;
|
||||||
|
@ -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
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const ownProps = Object.getOwnPropertyNames(Object.getPrototypeOf(from)).concat(
|
function extend(who: any, from: any, overwrite = true) {
|
||||||
Object.keys(from)
|
const ownProps = Object.getOwnPropertyNames(Object.getPrototypeOf(from)).concat(Object.keys(from));
|
||||||
);
|
ownProps.forEach((prop) => {
|
||||||
ownProps.forEach(prop => {
|
if (prop === "constructor" || from[prop] === undefined) return;
|
||||||
if (prop === 'constructor' || from[prop] === undefined) return;
|
if (who[prop] && overwrite) {
|
||||||
if (who[prop] && overwrite) {
|
who[`_${prop}`] = who[prop];
|
||||||
who[`_${prop}`] = who[prop];
|
}
|
||||||
}
|
if (typeof from[prop] === "function") who[prop] = from[prop].bind(who);
|
||||||
if (typeof from[prop] === 'function') who[prop] = from[prop].bind(who);
|
else who[prop] = from[prop];
|
||||||
else who[prop] = from[prop];
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function stob(stream: ReadStream): Promise<Buffer> {
|
function stob(stream: ReadStream): Promise<Buffer> {
|
||||||
return new Promise(resolve => {
|
return new Promise((resolve) => {
|
||||||
const buffers: Buffer[] = [];
|
const buffers: Buffer[] = [];
|
||||||
stream.on('data', buffers.push.bind(buffers));
|
stream.on("data", buffers.push.bind(buffers));
|
||||||
|
|
||||||
stream.on('end', () => {
|
stream.on("end", () => {
|
||||||
switch (buffers.length) {
|
switch (buffers.length) {
|
||||||
case 0:
|
case 0:
|
||||||
resolve(Buffer.allocUnsafe(0));
|
resolve(Buffer.allocUnsafe(0));
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
resolve(buffers[0]);
|
resolve(buffers[0]);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
resolve(Buffer.concat(buffers));
|
resolve(Buffer.concat(buffers));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { extend, stob };
|
export { extend, stob };
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import { parse } from 'query-string';
|
import { parse } from "query-string";
|
||||||
import { HttpRequest } from 'uWebSockets.js';
|
import { HttpRequest } from "uWebSockets.js";
|
||||||
import App from './server/app';
|
import App from "./server/app";
|
||||||
import SSLApp from './server/sslapp';
|
import SSLApp from "./server/sslapp";
|
||||||
import * as types from './server/types';
|
import * as types from "./server/types";
|
||||||
|
|
||||||
const getQuery = (req: HttpRequest) => {
|
const getQuery = (req: HttpRequest) => {
|
||||||
return parse(req.getQuery());
|
return parse(req.getQuery());
|
||||||
};
|
};
|
||||||
|
|
||||||
export { App, SSLApp, getQuery };
|
export { App, SSLApp, getQuery };
|
||||||
export * from './server/types';
|
export * from "./server/types";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
App,
|
App,
|
||||||
SSLApp,
|
SSLApp,
|
||||||
getQuery,
|
getQuery,
|
||||||
...types
|
...types,
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export const arrayIntersect = (array1: string[], array2: string[]) : boolean => {
|
export const arrayIntersect = (array1: string[], array2: string[]): boolean => {
|
||||||
return array1.filter(value => array2.includes(value)).length > 0;
|
return array1.filter((value) => array2.includes(value)).length > 0;
|
||||||
}
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const EventEmitter = require('events');
|
const EventEmitter = require("events");
|
||||||
|
|
||||||
const clientJoinEvent = 'clientJoin';
|
const clientJoinEvent = "clientJoin";
|
||||||
const clientLeaveEvent = 'clientLeave';
|
const clientLeaveEvent = "clientLeave";
|
||||||
|
|
||||||
class ClientEventsEmitter extends EventEmitter {
|
class ClientEventsEmitter extends EventEmitter {
|
||||||
emitClientJoin(clientUUid: string, roomId: string): void {
|
emitClientJoin(clientUUid: string, roomId: string): void {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {CPU_OVERHEAT_THRESHOLD} from "../Enum/EnvironmentVariable";
|
import { CPU_OVERHEAT_THRESHOLD } from "../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
function secNSec2ms(secNSec: Array<number>|number) {
|
function secNSec2ms(secNSec: Array<number> | number) {
|
||||||
if (Array.isArray(secNSec)) {
|
if (Array.isArray(secNSec)) {
|
||||||
return secNSec[0] * 1000 + secNSec[1] / 1000000;
|
return secNSec[0] * 1000 + secNSec[1] / 1000000;
|
||||||
}
|
}
|
||||||
@ -12,17 +12,17 @@ class CpuTracker {
|
|||||||
private overHeating: boolean = false;
|
private overHeating: boolean = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
let time = process.hrtime.bigint()
|
let time = process.hrtime.bigint();
|
||||||
let usage = process.cpuUsage()
|
let usage = process.cpuUsage();
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
const elapTime = process.hrtime.bigint();
|
const elapTime = process.hrtime.bigint();
|
||||||
const elapUsage = process.cpuUsage(usage)
|
const elapUsage = process.cpuUsage(usage);
|
||||||
usage = process.cpuUsage()
|
usage = process.cpuUsage();
|
||||||
|
|
||||||
const elapTimeMS = elapTime - time;
|
const elapTimeMS = elapTime - time;
|
||||||
const elapUserMS = secNSec2ms(elapUsage.user)
|
const elapUserMS = secNSec2ms(elapUsage.user);
|
||||||
const elapSystMS = secNSec2ms(elapUsage.system)
|
const elapSystMS = secNSec2ms(elapUsage.system);
|
||||||
this.cpuPercent = Math.round(100 * (elapUserMS + elapSystMS) / Number(elapTimeMS) * 1000000)
|
this.cpuPercent = Math.round(((100 * (elapUserMS + elapSystMS)) / Number(elapTimeMS)) * 1000000);
|
||||||
|
|
||||||
time = elapTime;
|
time = elapTime;
|
||||||
|
|
||||||
|
@ -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
|
//this class should manage all the custom metrics used by prometheus
|
||||||
class GaugeManager {
|
class GaugeManager {
|
||||||
@ -10,29 +10,29 @@ class GaugeManager {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.nbRoomsGauge = new Gauge({
|
this.nbRoomsGauge = new Gauge({
|
||||||
name: 'workadventure_nb_rooms',
|
name: "workadventure_nb_rooms",
|
||||||
help: 'Number of active rooms'
|
help: "Number of active rooms",
|
||||||
});
|
});
|
||||||
this.nbClientsGauge = new Gauge({
|
this.nbClientsGauge = new Gauge({
|
||||||
name: 'workadventure_nb_sockets',
|
name: "workadventure_nb_sockets",
|
||||||
help: 'Number of connected sockets',
|
help: "Number of connected sockets",
|
||||||
labelNames: [ ]
|
labelNames: [],
|
||||||
});
|
});
|
||||||
this.nbClientsPerRoomGauge = new Gauge({
|
this.nbClientsPerRoomGauge = new Gauge({
|
||||||
name: 'workadventure_nb_clients_per_room',
|
name: "workadventure_nb_clients_per_room",
|
||||||
help: 'Number of clients per room',
|
help: "Number of clients per room",
|
||||||
labelNames: [ 'room' ]
|
labelNames: ["room"],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.nbGroupsPerRoomCounter = new Counter({
|
this.nbGroupsPerRoomCounter = new Counter({
|
||||||
name: 'workadventure_counter_groups_per_room',
|
name: "workadventure_counter_groups_per_room",
|
||||||
help: 'Counter of groups per room',
|
help: "Counter of groups per room",
|
||||||
labelNames: [ 'room' ]
|
labelNames: ["room"],
|
||||||
});
|
});
|
||||||
this.nbGroupsPerRoomGauge = new Gauge({
|
this.nbGroupsPerRoomGauge = new Gauge({
|
||||||
name: 'workadventure_nb_groups_per_room',
|
name: "workadventure_nb_groups_per_room",
|
||||||
help: 'Number of groups per room',
|
help: "Number of groups per room",
|
||||||
labelNames: [ 'room' ]
|
labelNames: ["room"],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,13 +54,13 @@ class GaugeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
incNbGroupsPerRoomGauge(roomId: string): void {
|
incNbGroupsPerRoomGauge(roomId: string): void {
|
||||||
this.nbGroupsPerRoomCounter.inc({ room: roomId })
|
this.nbGroupsPerRoomCounter.inc({ room: roomId });
|
||||||
this.nbGroupsPerRoomGauge.inc({ room: roomId })
|
this.nbGroupsPerRoomGauge.inc({ room: roomId });
|
||||||
}
|
}
|
||||||
|
|
||||||
decNbGroupsPerRoomGauge(roomId: string): void {
|
decNbGroupsPerRoomGauge(roomId: string): void {
|
||||||
this.nbGroupsPerRoomGauge.dec({ room: roomId })
|
this.nbGroupsPerRoomGauge.dec({ room: roomId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const gaugeManager = new GaugeManager();
|
export const gaugeManager = new GaugeManager();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {ErrorMessage, ServerToClientMessage} from "../Messages/generated/messages_pb";
|
import { ErrorMessage, ServerToClientMessage } from "../Messages/generated/messages_pb";
|
||||||
import {UserSocket} from "_Model/User";
|
import { UserSocket } from "_Model/User";
|
||||||
|
|
||||||
export function emitError(Client: UserSocket, message: string): void {
|
export function emitError(Client: UserSocket, message: string): void {
|
||||||
const errorMessage = new ErrorMessage();
|
const errorMessage = new ErrorMessage();
|
||||||
@ -9,7 +9,7 @@ export function emitError(Client: UserSocket, message: string): void {
|
|||||||
serverToClientMessage.setErrormessage(errorMessage);
|
serverToClientMessage.setErrormessage(errorMessage);
|
||||||
|
|
||||||
//if (!Client.disconnecting) {
|
//if (!Client.disconnecting) {
|
||||||
Client.write(serverToClientMessage);
|
Client.write(serverToClientMessage);
|
||||||
//}
|
//}
|
||||||
console.warn(message);
|
console.warn(message);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {GameRoom} from "../Model/GameRoom";
|
import { GameRoom } from "../Model/GameRoom";
|
||||||
import {
|
import {
|
||||||
ItemEventMessage,
|
ItemEventMessage,
|
||||||
ItemStateMessage,
|
ItemStateMessage,
|
||||||
@ -27,39 +27,39 @@ import {
|
|||||||
WorldFullWarningMessage,
|
WorldFullWarningMessage,
|
||||||
UserLeftZoneMessage,
|
UserLeftZoneMessage,
|
||||||
EmoteEventMessage,
|
EmoteEventMessage,
|
||||||
BanUserMessage, RefreshRoomMessage, EmotePromptMessage,
|
BanUserMessage,
|
||||||
|
RefreshRoomMessage,
|
||||||
|
EmotePromptMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import {User, UserSocket} from "../Model/User";
|
import { User, UserSocket } from "../Model/User";
|
||||||
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
||||||
import {Group} from "../Model/Group";
|
import { Group } from "../Model/Group";
|
||||||
import {cpuTracker} from "./CpuTracker";
|
import { cpuTracker } from "./CpuTracker";
|
||||||
import {
|
import {
|
||||||
GROUP_RADIUS,
|
GROUP_RADIUS,
|
||||||
JITSI_ISS,
|
JITSI_ISS,
|
||||||
MINIMUM_DISTANCE,
|
MINIMUM_DISTANCE,
|
||||||
SECRET_JITSI_KEY,
|
SECRET_JITSI_KEY,
|
||||||
TURN_STATIC_AUTH_SECRET
|
TURN_STATIC_AUTH_SECRET,
|
||||||
} from "../Enum/EnvironmentVariable";
|
} from "../Enum/EnvironmentVariable";
|
||||||
import {Movable} from "../Model/Movable";
|
import { Movable } from "../Model/Movable";
|
||||||
import {PositionInterface} from "../Model/PositionInterface";
|
import { PositionInterface } from "../Model/PositionInterface";
|
||||||
import Jwt from "jsonwebtoken";
|
import Jwt from "jsonwebtoken";
|
||||||
import {JITSI_URL} from "../Enum/EnvironmentVariable";
|
import { JITSI_URL } from "../Enum/EnvironmentVariable";
|
||||||
import {clientEventsEmitter} from "./ClientEventsEmitter";
|
import { clientEventsEmitter } from "./ClientEventsEmitter";
|
||||||
import {gaugeManager} from "./GaugeManager";
|
import { gaugeManager } from "./GaugeManager";
|
||||||
import {ZoneSocket} from "../RoomManager";
|
import { ZoneSocket } from "../RoomManager";
|
||||||
import {Zone} from "_Model/Zone";
|
import { Zone } from "_Model/Zone";
|
||||||
import Debug from "debug";
|
import Debug from "debug";
|
||||||
import {Admin} from "_Model/Admin";
|
import { Admin } from "_Model/Admin";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
const debug = Debug("sockermanager");
|
||||||
const debug = Debug('sockermanager');
|
|
||||||
|
|
||||||
function emitZoneMessage(subMessage: SubToPusherMessage, socket: ZoneSocket): void {
|
function emitZoneMessage(subMessage: SubToPusherMessage, socket: ZoneSocket): void {
|
||||||
// TODO: should we batch those every 100ms?
|
// TODO: should we batch those every 100ms?
|
||||||
const batchMessage = new BatchToPusherMessage();
|
const batchMessage = new BatchToPusherMessage();
|
||||||
batchMessage.addPayload(subMessage);
|
batchMessage.addPayload(subMessage);
|
||||||
|
|
||||||
|
|
||||||
socket.write(batchMessage);
|
socket.write(batchMessage);
|
||||||
}
|
}
|
||||||
@ -68,7 +68,6 @@ export class SocketManager {
|
|||||||
private rooms: Map<string, GameRoom> = new Map<string, GameRoom>();
|
private rooms: Map<string, GameRoom> = new Map<string, GameRoom>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
||||||
clientEventsEmitter.registerToClientJoin((clientUUid: string, roomId: string) => {
|
clientEventsEmitter.registerToClientJoin((clientUUid: string, roomId: string) => {
|
||||||
gaugeManager.incNbClientPerRoomGauge(roomId);
|
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
|
//join new previous room
|
||||||
const {room, user} = await this.joinRoom(socket, joinRoomMessage);
|
const { room, user } = await this.joinRoom(socket, joinRoomMessage);
|
||||||
|
|
||||||
if (!socket.writable) {
|
if (!socket.writable) {
|
||||||
console.warn('Socket was aborted');
|
console.warn("Socket was aborted");
|
||||||
return {
|
return {
|
||||||
room,
|
room,
|
||||||
user
|
user,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const roomJoinedMessage = new RoomJoinedMessage();
|
const roomJoinedMessage = new RoomJoinedMessage();
|
||||||
@ -108,9 +109,8 @@ export class SocketManager {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
room,
|
room,
|
||||||
user
|
user,
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUserMovesMessage(room: GameRoom, user: User, userMovesMessage: UserMovesMessage) {
|
handleUserMovesMessage(room: GameRoom, user: User, userMovesMessage: UserMovesMessage) {
|
||||||
@ -124,13 +124,12 @@ export class SocketManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (position === undefined) {
|
if (position === undefined) {
|
||||||
throw new Error('Position not found in message');
|
throw new Error("Position not found in message");
|
||||||
}
|
}
|
||||||
const viewport = userMoves.viewport;
|
const viewport = userMoves.viewport;
|
||||||
if (viewport === undefined) {
|
if (viewport === undefined) {
|
||||||
throw new Error('Viewport not found in message');
|
throw new Error("Viewport not found in message");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// update position in the world
|
// update position in the world
|
||||||
room.updatePosition(user, ProtobufUtils.toPointInterface(position));
|
room.updatePosition(user, ProtobufUtils.toPointInterface(position));
|
||||||
@ -189,7 +188,11 @@ export class SocketManager {
|
|||||||
//send only at user
|
//send only at user
|
||||||
const remoteUser = room.getUsers().get(data.getReceiverid());
|
const remoteUser = room.getUsers().get(data.getReceiverid());
|
||||||
if (remoteUser === undefined) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,8 +200,8 @@ export class SocketManager {
|
|||||||
webrtcSignalToClient.setUserid(user.id);
|
webrtcSignalToClient.setUserid(user.id);
|
||||||
webrtcSignalToClient.setSignal(data.getSignal());
|
webrtcSignalToClient.setSignal(data.getSignal());
|
||||||
// TODO: only compute credentials if data.signal.type === "offer"
|
// TODO: only compute credentials if data.signal.type === "offer"
|
||||||
if (TURN_STATIC_AUTH_SECRET !== '') {
|
if (TURN_STATIC_AUTH_SECRET !== "") {
|
||||||
const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET);
|
const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET);
|
||||||
webrtcSignalToClient.setWebrtcusername(username);
|
webrtcSignalToClient.setWebrtcusername(username);
|
||||||
webrtcSignalToClient.setWebrtcpassword(password);
|
webrtcSignalToClient.setWebrtcpassword(password);
|
||||||
}
|
}
|
||||||
@ -207,7 +210,7 @@ export class SocketManager {
|
|||||||
serverToClientMessage.setWebrtcsignaltoclientmessage(webrtcSignalToClient);
|
serverToClientMessage.setWebrtcsignaltoclientmessage(webrtcSignalToClient);
|
||||||
|
|
||||||
//if (!client.disconnecting) {
|
//if (!client.disconnecting) {
|
||||||
remoteUser.socket.write(serverToClientMessage);
|
remoteUser.socket.write(serverToClientMessage);
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,7 +218,11 @@ export class SocketManager {
|
|||||||
//send only at user
|
//send only at user
|
||||||
const remoteUser = room.getUsers().get(data.getReceiverid());
|
const remoteUser = room.getUsers().get(data.getReceiverid());
|
||||||
if (remoteUser === undefined) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,8 +230,8 @@ export class SocketManager {
|
|||||||
webrtcSignalToClient.setUserid(user.id);
|
webrtcSignalToClient.setUserid(user.id);
|
||||||
webrtcSignalToClient.setSignal(data.getSignal());
|
webrtcSignalToClient.setSignal(data.getSignal());
|
||||||
// TODO: only compute credentials if data.signal.type === "offer"
|
// TODO: only compute credentials if data.signal.type === "offer"
|
||||||
if (TURN_STATIC_AUTH_SECRET !== '') {
|
if (TURN_STATIC_AUTH_SECRET !== "") {
|
||||||
const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET);
|
const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET);
|
||||||
webrtcSignalToClient.setWebrtcusername(username);
|
webrtcSignalToClient.setWebrtcusername(username);
|
||||||
webrtcSignalToClient.setWebrtcpassword(password);
|
webrtcSignalToClient.setWebrtcpassword(password);
|
||||||
}
|
}
|
||||||
@ -233,11 +240,11 @@ export class SocketManager {
|
|||||||
serverToClientMessage.setWebrtcscreensharingsignaltoclientmessage(webrtcSignalToClient);
|
serverToClientMessage.setWebrtcscreensharingsignaltoclientmessage(webrtcSignalToClient);
|
||||||
|
|
||||||
//if (!client.disconnecting) {
|
//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
|
// leave previous room and world
|
||||||
try {
|
try {
|
||||||
//user leave previous world
|
//user leave previous world
|
||||||
@ -249,33 +256,39 @@ export class SocketManager {
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
clientEventsEmitter.emitClientLeave(user.uuid, room.roomId);
|
clientEventsEmitter.emitClientLeave(user.uuid, room.roomId);
|
||||||
console.log('A user left');
|
console.log("A user left");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOrCreateRoom(roomId: string): Promise<GameRoom> {
|
async getOrCreateRoom(roomId: string): Promise<GameRoom> {
|
||||||
//check and create new world for a room
|
//check and create new world for a room
|
||||||
let world = this.rooms.get(roomId)
|
let world = this.rooms.get(roomId);
|
||||||
if(world === undefined){
|
if (world === undefined) {
|
||||||
world = new GameRoom(
|
world = new GameRoom(
|
||||||
roomId,
|
roomId,
|
||||||
(user: User, group: Group) => this.joinWebRtcRoom(user, group),
|
(user: User, group: Group) => this.joinWebRtcRoom(user, group),
|
||||||
(user: User, group: Group) => this.disConnectedUser(user, group),
|
(user: User, group: Group) => this.disConnectedUser(user, group),
|
||||||
MINIMUM_DISTANCE,
|
MINIMUM_DISTANCE,
|
||||||
GROUP_RADIUS,
|
GROUP_RADIUS,
|
||||||
(thing: Movable, fromZone: Zone|null, listener: ZoneSocket) => this.onZoneEnter(thing, fromZone, listener),
|
(thing: Movable, fromZone: Zone | null, listener: ZoneSocket) =>
|
||||||
(thing: Movable, position:PositionInterface, listener: ZoneSocket) => this.onClientMove(thing, position, listener),
|
this.onZoneEnter(thing, fromZone, listener),
|
||||||
(thing: Movable, newZone: Zone|null, listener: ZoneSocket) => this.onClientLeave(thing, newZone, listener),
|
(thing: Movable, position: PositionInterface, listener: ZoneSocket) =>
|
||||||
(emoteEventMessage:EmoteEventMessage, listener: ZoneSocket) => this.onEmote(emoteEventMessage, listener),
|
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();
|
gaugeManager.incNbRoomGauge();
|
||||||
this.rooms.set(roomId, world);
|
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 roomId = joinRoomMessage.getRoomid();
|
||||||
|
|
||||||
const room = await socketManager.getOrCreateRoom(roomId);
|
const room = await socketManager.getOrCreateRoom(roomId);
|
||||||
@ -284,15 +297,15 @@ export class SocketManager {
|
|||||||
const user = room.join(socket, joinRoomMessage);
|
const user = room.join(socket, joinRoomMessage);
|
||||||
|
|
||||||
clientEventsEmitter.emitClientJoin(user.uuid, roomId);
|
clientEventsEmitter.emitClientJoin(user.uuid, roomId);
|
||||||
console.log(new Date().toISOString() + ' A user joined');
|
console.log(new Date().toISOString() + " A user joined");
|
||||||
return {room, user};
|
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) {
|
if (thing instanceof User) {
|
||||||
const userJoinedZoneMessage = new UserJoinedZoneMessage();
|
const userJoinedZoneMessage = new UserJoinedZoneMessage();
|
||||||
if (!Number.isInteger(thing.id)) {
|
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.setUserid(thing.id);
|
||||||
userJoinedZoneMessage.setName(thing.name);
|
userJoinedZoneMessage.setName(thing.name);
|
||||||
@ -312,11 +325,11 @@ export class SocketManager {
|
|||||||
} else if (thing instanceof Group) {
|
} else if (thing instanceof Group) {
|
||||||
this.emitCreateUpdateGroupEvent(listener, fromZone, thing);
|
this.emitCreateUpdateGroupEvent(listener, fromZone, thing);
|
||||||
} else {
|
} 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) {
|
if (thing instanceof User) {
|
||||||
const userMovedMessage = new UserMovedMessage();
|
const userMovedMessage = new UserMovedMessage();
|
||||||
userMovedMessage.setUserid(thing.id);
|
userMovedMessage.setUserid(thing.id);
|
||||||
@ -331,21 +344,20 @@ export class SocketManager {
|
|||||||
} else if (thing instanceof Group) {
|
} else if (thing instanceof Group) {
|
||||||
this.emitCreateUpdateGroupEvent(listener, null, thing);
|
this.emitCreateUpdateGroupEvent(listener, null, thing);
|
||||||
} else {
|
} 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) {
|
if (thing instanceof User) {
|
||||||
this.emitUserLeftEvent(listener, thing.id, newZone);
|
this.emitUserLeftEvent(listener, thing.id, newZone);
|
||||||
} else if (thing instanceof Group) {
|
} else if (thing instanceof Group) {
|
||||||
this.emitDeleteGroupEvent(listener, thing.getId(), newZone);
|
this.emitDeleteGroupEvent(listener, thing.getId(), newZone);
|
||||||
} else {
|
} else {
|
||||||
console.error('Unexpected type for Movable.');
|
console.error("Unexpected type for Movable.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private onEmote(emoteEventMessage: EmoteEventMessage, client: ZoneSocket) {
|
private onEmote(emoteEventMessage: EmoteEventMessage, client: ZoneSocket) {
|
||||||
const subMessage = new SubToPusherMessage();
|
const subMessage = new SubToPusherMessage();
|
||||||
subMessage.setEmoteeventmessage(emoteEventMessage);
|
subMessage.setEmoteeventmessage(emoteEventMessage);
|
||||||
@ -353,7 +365,7 @@ export class SocketManager {
|
|||||||
emitZoneMessage(subMessage, client);
|
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 position = group.getPosition();
|
||||||
const pointMessage = new PointMessage();
|
const pointMessage = new PointMessage();
|
||||||
pointMessage.setX(Math.floor(position.x));
|
pointMessage.setX(Math.floor(position.x));
|
||||||
@ -371,7 +383,7 @@ export class SocketManager {
|
|||||||
//client.emitInBatch(subMessage);
|
//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();
|
const groupDeleteMessage = new GroupLeftZoneMessage();
|
||||||
groupDeleteMessage.setGroupid(groupId);
|
groupDeleteMessage.setGroupid(groupId);
|
||||||
groupDeleteMessage.setTozone(this.toProtoZone(newZone));
|
groupDeleteMessage.setTozone(this.toProtoZone(newZone));
|
||||||
@ -383,7 +395,7 @@ export class SocketManager {
|
|||||||
//user.emitInBatch(subMessage);
|
//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();
|
const userLeftMessage = new UserLeftZoneMessage();
|
||||||
userLeftMessage.setUserid(userId);
|
userLeftMessage.setUserid(userId);
|
||||||
userLeftMessage.setTozone(this.toProtoZone(newZone));
|
userLeftMessage.setTozone(this.toProtoZone(newZone));
|
||||||
@ -394,7 +406,7 @@ export class SocketManager {
|
|||||||
emitZoneMessage(subMessage, client);
|
emitZoneMessage(subMessage, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
private toProtoZone(zone: Zone|null): ProtoZone|undefined {
|
private toProtoZone(zone: Zone | null): ProtoZone | undefined {
|
||||||
if (zone !== null) {
|
if (zone !== null) {
|
||||||
const zoneMessage = new ProtoZone();
|
const zoneMessage = new ProtoZone();
|
||||||
zoneMessage.setX(zone.x);
|
zoneMessage.setX(zone.x);
|
||||||
@ -405,7 +417,6 @@ export class SocketManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private joinWebRtcRoom(user: User, group: Group) {
|
private joinWebRtcRoom(user: User, group: Group) {
|
||||||
|
|
||||||
for (const otherUser of group.getUsers()) {
|
for (const otherUser of group.getUsers()) {
|
||||||
if (user === otherUser) {
|
if (user === otherUser) {
|
||||||
continue;
|
continue;
|
||||||
@ -416,8 +427,8 @@ export class SocketManager {
|
|||||||
webrtcStartMessage1.setUserid(otherUser.id);
|
webrtcStartMessage1.setUserid(otherUser.id);
|
||||||
webrtcStartMessage1.setName(otherUser.name);
|
webrtcStartMessage1.setName(otherUser.name);
|
||||||
webrtcStartMessage1.setInitiator(true);
|
webrtcStartMessage1.setInitiator(true);
|
||||||
if (TURN_STATIC_AUTH_SECRET !== '') {
|
if (TURN_STATIC_AUTH_SECRET !== "") {
|
||||||
const {username, password} = this.getTURNCredentials(''+otherUser.id, TURN_STATIC_AUTH_SECRET);
|
const { username, password } = this.getTURNCredentials("" + otherUser.id, TURN_STATIC_AUTH_SECRET);
|
||||||
webrtcStartMessage1.setWebrtcusername(username);
|
webrtcStartMessage1.setWebrtcusername(username);
|
||||||
webrtcStartMessage1.setWebrtcpassword(password);
|
webrtcStartMessage1.setWebrtcpassword(password);
|
||||||
}
|
}
|
||||||
@ -426,16 +437,16 @@ export class SocketManager {
|
|||||||
serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1);
|
serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1);
|
||||||
|
|
||||||
//if (!user.socket.disconnecting) {
|
//if (!user.socket.disconnecting) {
|
||||||
user.socket.write(serverToClientMessage1);
|
user.socket.write(serverToClientMessage1);
|
||||||
//console.log('Sending webrtcstart initiator to '+user.socket.userId)
|
//console.log('Sending webrtcstart initiator to '+user.socket.userId)
|
||||||
//}
|
//}
|
||||||
|
|
||||||
const webrtcStartMessage2 = new WebRtcStartMessage();
|
const webrtcStartMessage2 = new WebRtcStartMessage();
|
||||||
webrtcStartMessage2.setUserid(user.id);
|
webrtcStartMessage2.setUserid(user.id);
|
||||||
webrtcStartMessage2.setName(user.name);
|
webrtcStartMessage2.setName(user.name);
|
||||||
webrtcStartMessage2.setInitiator(false);
|
webrtcStartMessage2.setInitiator(false);
|
||||||
if (TURN_STATIC_AUTH_SECRET !== '') {
|
if (TURN_STATIC_AUTH_SECRET !== "") {
|
||||||
const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET);
|
const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET);
|
||||||
webrtcStartMessage2.setWebrtcusername(username);
|
webrtcStartMessage2.setWebrtcusername(username);
|
||||||
webrtcStartMessage2.setWebrtcpassword(password);
|
webrtcStartMessage2.setWebrtcpassword(password);
|
||||||
}
|
}
|
||||||
@ -444,10 +455,9 @@ export class SocketManager {
|
|||||||
serverToClientMessage2.setWebrtcstartmessage(webrtcStartMessage2);
|
serverToClientMessage2.setWebrtcstartmessage(webrtcStartMessage2);
|
||||||
|
|
||||||
//if (!otherUser.socket.disconnecting) {
|
//if (!otherUser.socket.disconnecting) {
|
||||||
otherUser.socket.write(serverToClientMessage2);
|
otherUser.socket.write(serverToClientMessage2);
|
||||||
//console.log('Sending webrtcstart to '+otherUser.socket.userId)
|
//console.log('Sending webrtcstart to '+otherUser.socket.userId)
|
||||||
//}
|
//}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,17 +466,17 @@ export class SocketManager {
|
|||||||
* and the Coturn server.
|
* and the Coturn server.
|
||||||
* The Coturn server should be initialized with parameters: `--use-auth-secret --static-auth-secret=MySecretKey`
|
* 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} {
|
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 unixTimeStamp = Math.floor(Date.now() / 1000) + 4 * 3600; // this credential would be valid for the next 4 hours
|
||||||
const username = [unixTimeStamp, name].join(':');
|
const username = [unixTimeStamp, name].join(":");
|
||||||
const hmac = crypto.createHmac('sha1', secret);
|
const hmac = crypto.createHmac("sha1", secret);
|
||||||
hmac.setEncoding('base64');
|
hmac.setEncoding("base64");
|
||||||
hmac.write(username);
|
hmac.write(username);
|
||||||
hmac.end();
|
hmac.end();
|
||||||
const password = hmac.read();
|
const password = hmac.read();
|
||||||
return {
|
return {
|
||||||
username: username,
|
username: username,
|
||||||
password: password
|
password: password,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -489,10 +499,9 @@ export class SocketManager {
|
|||||||
serverToClientMessage1.setWebrtcdisconnectmessage(webrtcDisconnectMessage1);
|
serverToClientMessage1.setWebrtcdisconnectmessage(webrtcDisconnectMessage1);
|
||||||
|
|
||||||
//if (!otherUser.socket.disconnecting) {
|
//if (!otherUser.socket.disconnecting) {
|
||||||
otherUser.socket.write(serverToClientMessage1);
|
otherUser.socket.write(serverToClientMessage1);
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
|
||||||
const webrtcDisconnectMessage2 = new WebRtcDisconnectMessage();
|
const webrtcDisconnectMessage2 = new WebRtcDisconnectMessage();
|
||||||
webrtcDisconnectMessage2.setUserid(otherUser.id);
|
webrtcDisconnectMessage2.setUserid(otherUser.id);
|
||||||
|
|
||||||
@ -500,7 +509,7 @@ export class SocketManager {
|
|||||||
serverToClientMessage2.setWebrtcdisconnectmessage(webrtcDisconnectMessage2);
|
serverToClientMessage2.setWebrtcdisconnectmessage(webrtcDisconnectMessage2);
|
||||||
|
|
||||||
//if (!user.socket.disconnecting) {
|
//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('An error occurred on "emitPlayGlobalMessage" event');
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getWorlds(): Map<string, GameRoom> {
|
public getWorlds(): Map<string, GameRoom> {
|
||||||
return this.rooms;
|
return this.rooms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public handleQueryJitsiJwtMessage(user: User, queryJitsiJwtMessage: QueryJitsiJwtMessage) {
|
public handleQueryJitsiJwtMessage(user: User, queryJitsiJwtMessage: QueryJitsiJwtMessage) {
|
||||||
const room = queryJitsiJwtMessage.getJitsiroom();
|
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.
|
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 === '') {
|
if (SECRET_JITSI_KEY === "") {
|
||||||
throw new Error('You must set the SECRET_JITSI_KEY key to the secret to generate JWT tokens for Jitsi.');
|
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
|
// Let's see if the current client has
|
||||||
const isAdmin = user.tags.includes(tag);
|
const isAdmin = user.tags.includes(tag);
|
||||||
|
|
||||||
const jwt = Jwt.sign({
|
const jwt = Jwt.sign(
|
||||||
"aud": "jitsi",
|
{
|
||||||
"iss": JITSI_ISS,
|
aud: "jitsi",
|
||||||
"sub": JITSI_URL,
|
iss: JITSI_ISS,
|
||||||
"room": room,
|
sub: JITSI_URL,
|
||||||
"moderator": isAdmin
|
room: room,
|
||||||
}, SECRET_JITSI_KEY, {
|
moderator: isAdmin,
|
||||||
expiresIn: '1d',
|
},
|
||||||
algorithm: "HS256",
|
SECRET_JITSI_KEY,
|
||||||
header:
|
{
|
||||||
{
|
expiresIn: "1d",
|
||||||
"alg": "HS256",
|
algorithm: "HS256",
|
||||||
"typ": "JWT"
|
header: {
|
||||||
}
|
alg: "HS256",
|
||||||
});
|
typ: "JWT",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const sendJitsiJwtMessage = new SendJitsiJwtMessage();
|
const sendJitsiJwtMessage = new SendJitsiJwtMessage();
|
||||||
sendJitsiJwtMessage.setJitsiroom(room);
|
sendJitsiJwtMessage.setJitsiroom(room);
|
||||||
@ -562,7 +572,7 @@ export class SocketManager {
|
|||||||
user.socket.write(serverToClientMessage);
|
user.socket.write(serverToClientMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public handlerSendUserMessage(user: User, sendUserMessageToSend: SendUserMessage){
|
public handlerSendUserMessage(user: User, sendUserMessageToSend: SendUserMessage) {
|
||||||
const sendUserMessage = new SendUserMessage();
|
const sendUserMessage = new SendUserMessage();
|
||||||
sendUserMessage.setMessage(sendUserMessageToSend.getMessage());
|
sendUserMessage.setMessage(sendUserMessageToSend.getMessage());
|
||||||
sendUserMessage.setType(sendUserMessageToSend.getType());
|
sendUserMessage.setType(sendUserMessageToSend.getType());
|
||||||
@ -572,7 +582,7 @@ export class SocketManager {
|
|||||||
user.socket.write(serverToClientMessage);
|
user.socket.write(serverToClientMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public handlerBanUserMessage(room: GameRoom, user: User, banUserMessageToSend: BanUserMessage){
|
public handlerBanUserMessage(room: GameRoom, user: User, banUserMessageToSend: BanUserMessage) {
|
||||||
const banUserMessage = new BanUserMessage();
|
const banUserMessage = new BanUserMessage();
|
||||||
banUserMessage.setMessage(banUserMessageToSend.getMessage());
|
banUserMessage.setMessage(banUserMessageToSend.getMessage());
|
||||||
banUserMessage.setType(banUserMessageToSend.getType());
|
banUserMessage.setType(banUserMessageToSend.getType());
|
||||||
@ -592,7 +602,7 @@ export class SocketManager {
|
|||||||
public addZoneListener(call: ZoneSocket, roomId: string, x: number, y: number): void {
|
public addZoneListener(call: ZoneSocket, roomId: string, x: number, y: number): void {
|
||||||
const room = this.rooms.get(roomId);
|
const room = this.rooms.get(roomId);
|
||||||
if (!room) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -636,7 +646,7 @@ export class SocketManager {
|
|||||||
removeZoneListener(call: ZoneSocket, roomId: string, x: number, y: number) {
|
removeZoneListener(call: ZoneSocket, roomId: string, x: number, y: number) {
|
||||||
const room = this.rooms.get(roomId);
|
const room = this.rooms.get(roomId);
|
||||||
if (!room) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,7 +661,7 @@ export class SocketManager {
|
|||||||
return room;
|
return room;
|
||||||
}
|
}
|
||||||
|
|
||||||
public leaveAdminRoom(room: GameRoom, admin: Admin){
|
public leaveAdminRoom(room: GameRoom, admin: Admin) {
|
||||||
room.adminLeave(admin);
|
room.adminLeave(admin);
|
||||||
if (room.isEmpty()) {
|
if (room.isEmpty()) {
|
||||||
this.rooms.delete(room.roomId);
|
this.rooms.delete(room.roomId);
|
||||||
@ -663,19 +673,27 @@ export class SocketManager {
|
|||||||
public sendAdminMessage(roomId: string, recipientUuid: string, message: string): void {
|
public sendAdminMessage(roomId: string, recipientUuid: string, message: string): void {
|
||||||
const room = this.rooms.get(roomId);
|
const room = this.rooms.get(roomId);
|
||||||
if (!room) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const recipient = room.getUserByUuid(recipientUuid);
|
const recipient = room.getUserByUuid(recipientUuid);
|
||||||
if (recipient === undefined) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendUserMessage = new SendUserMessage();
|
const sendUserMessage = new SendUserMessage();
|
||||||
sendUserMessage.setMessage(message);
|
sendUserMessage.setMessage(message);
|
||||||
sendUserMessage.setType('ban'); //todo: is the type correct?
|
sendUserMessage.setType("ban"); //todo: is the type correct?
|
||||||
|
|
||||||
const serverToClientMessage = new ServerToClientMessage();
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
serverToClientMessage.setSendusermessage(sendUserMessage);
|
serverToClientMessage.setSendusermessage(sendUserMessage);
|
||||||
@ -686,13 +704,21 @@ export class SocketManager {
|
|||||||
public banUser(roomId: string, recipientUuid: string, message: string): void {
|
public banUser(roomId: string, recipientUuid: string, message: string): void {
|
||||||
const room = this.rooms.get(roomId);
|
const room = this.rooms.get(roomId);
|
||||||
if (!room) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const recipient = room.getUserByUuid(recipientUuid);
|
const recipient = room.getUserByUuid(recipientUuid);
|
||||||
if (recipient === undefined) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -701,7 +727,7 @@ export class SocketManager {
|
|||||||
|
|
||||||
const banUserMessage = new BanUserMessage();
|
const banUserMessage = new BanUserMessage();
|
||||||
banUserMessage.setMessage(message);
|
banUserMessage.setMessage(message);
|
||||||
banUserMessage.setType('banned');
|
banUserMessage.setType("banned");
|
||||||
|
|
||||||
const serverToClientMessage = new ServerToClientMessage();
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
serverToClientMessage.setBanusermessage(banUserMessage);
|
serverToClientMessage.setBanusermessage(banUserMessage);
|
||||||
@ -711,19 +737,22 @@ export class SocketManager {
|
|||||||
recipient.socket.end();
|
recipient.socket.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sendAdminRoomMessage(roomId: string, message: string) {
|
sendAdminRoomMessage(roomId: string, message: string) {
|
||||||
const room = this.rooms.get(roomId);
|
const room = this.rooms.get(roomId);
|
||||||
if (!room) {
|
if (!room) {
|
||||||
//todo: this should cause the http call to return a 500
|
//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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
room.getUsers().forEach((recipient) => {
|
room.getUsers().forEach((recipient) => {
|
||||||
const sendUserMessage = new SendUserMessage();
|
const sendUserMessage = new SendUserMessage();
|
||||||
sendUserMessage.setMessage(message);
|
sendUserMessage.setMessage(message);
|
||||||
sendUserMessage.setType('message');
|
sendUserMessage.setType("message");
|
||||||
|
|
||||||
const clientMessage = new ServerToClientMessage();
|
const clientMessage = new ServerToClientMessage();
|
||||||
clientMessage.setSendusermessage(sendUserMessage);
|
clientMessage.setSendusermessage(sendUserMessage);
|
||||||
@ -732,14 +761,18 @@ export class SocketManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchWorlFullWarning(roomId: string,): void {
|
dispatchWorlFullWarning(roomId: string): void {
|
||||||
const room = this.rooms.get(roomId);
|
const room = this.rooms.get(roomId);
|
||||||
if (!room) {
|
if (!room) {
|
||||||
//todo: this should cause the http call to return a 500
|
//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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
room.getUsers().forEach((recipient) => {
|
room.getUsers().forEach((recipient) => {
|
||||||
const worldFullMessage = new WorldFullWarningMessage();
|
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);
|
const room = this.rooms.get(roomId);
|
||||||
if (!room) {
|
if (!room) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const versionNumber = room.incrementVersion();
|
const versionNumber = room.incrementVersion();
|
||||||
room.getUsers().forEach((recipient) => {
|
room.getUsers().forEach((recipient) => {
|
||||||
const worldFullMessage = new RefreshRoomMessage();
|
const worldFullMessage = new RefreshRoomMessage();
|
||||||
worldFullMessage.setRoomid(roomId)
|
worldFullMessage.setRoomid(roomId);
|
||||||
worldFullMessage.setVersionnumber(versionNumber)
|
worldFullMessage.setVersionnumber(versionNumber);
|
||||||
|
|
||||||
const clientMessage = new ServerToClientMessage();
|
const clientMessage = new ServerToClientMessage();
|
||||||
clientMessage.setRefreshroommessage(worldFullMessage);
|
clientMessage.setRefreshroommessage(worldFullMessage);
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
// lib/app.ts
|
// lib/app.ts
|
||||||
import {IoSocketController} from "./Controller/IoSocketController"; //TODO fix import by "_Controller/..."
|
import { IoSocketController } from "./Controller/IoSocketController"; //TODO fix import by "_Controller/..."
|
||||||
import {AuthenticateController} from "./Controller/AuthenticateController"; //TODO fix import by "_Controller/..."
|
import { AuthenticateController } from "./Controller/AuthenticateController"; //TODO fix import by "_Controller/..."
|
||||||
import {MapController} from "./Controller/MapController";
|
import { MapController } from "./Controller/MapController";
|
||||||
import {PrometheusController} from "./Controller/PrometheusController";
|
import { PrometheusController } from "./Controller/PrometheusController";
|
||||||
import {DebugController} from "./Controller/DebugController";
|
import { DebugController } from "./Controller/DebugController";
|
||||||
import {App as uwsApp} from "./Server/sifrr.server";
|
import { App as uwsApp } from "./Server/sifrr.server";
|
||||||
import {AdminController} from "./Controller/AdminController";
|
import { AdminController } from "./Controller/AdminController";
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
public app: uwsApp;
|
public app: uwsApp;
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
import {BaseController} from "./BaseController";
|
import { BaseController } from "./BaseController";
|
||||||
import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js";
|
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
||||||
import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_TOKEN } from "../Enum/EnvironmentVariable";
|
||||||
import {apiClientRepository} from "../Services/ApiClientRepository";
|
import { apiClientRepository } from "../Services/ApiClientRepository";
|
||||||
import {AdminRoomMessage, WorldFullWarningToRoomMessage, RefreshRoomPromptMessage} from "../Messages/generated/messages_pb";
|
import {
|
||||||
|
AdminRoomMessage,
|
||||||
|
WorldFullWarningToRoomMessage,
|
||||||
|
RefreshRoomPromptMessage,
|
||||||
|
} from "../Messages/generated/messages_pb";
|
||||||
|
|
||||||
|
export class AdminController extends BaseController {
|
||||||
export class AdminController extends BaseController{
|
constructor(private App: TemplatedApp) {
|
||||||
|
|
||||||
constructor(private App : TemplatedApp) {
|
|
||||||
super();
|
super();
|
||||||
this.App = App;
|
this.App = App;
|
||||||
this.receiveGlobalMessagePrompt();
|
this.receiveGlobalMessagePrompt();
|
||||||
this.receiveRoomEditionPrompt();
|
this.receiveRoomEditionPrompt();
|
||||||
}
|
}
|
||||||
|
|
||||||
receiveRoomEditionPrompt() {
|
receiveRoomEditionPrompt() {
|
||||||
this.App.options("/room/refresh", (res: HttpResponse, req: HttpRequest) => {
|
this.App.options("/room/refresh", (res: HttpResponse, req: HttpRequest) => {
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
@ -23,25 +25,25 @@ export class AdminController extends BaseController{
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
this.App.post("/room/refresh", async (res: HttpResponse, req: HttpRequest) => {
|
this.App.post("/room/refresh", async (res: HttpResponse, req: HttpRequest) => {
|
||||||
res.onAborted(() => {
|
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();
|
const body = await res.json();
|
||||||
|
|
||||||
if (token !== ADMIN_API_TOKEN) {
|
if (token !== ADMIN_API_TOKEN) {
|
||||||
console.error('Admin access refused for token: '+token)
|
console.error("Admin access refused for token: " + token);
|
||||||
res.writeStatus("401 Unauthorized").end('Incorrect token');
|
res.writeStatus("401 Unauthorized").end("Incorrect token");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (typeof body.roomId !== 'string') {
|
if (typeof body.roomId !== "string") {
|
||||||
throw 'Incorrect roomId parameter'
|
throw "Incorrect roomId parameter";
|
||||||
}
|
}
|
||||||
const roomId: string = body.roomId;
|
const roomId: string = body.roomId;
|
||||||
|
|
||||||
await apiClientRepository.getClient(roomId).then((roomClient) =>{
|
await apiClientRepository.getClient(roomId).then((roomClient) => {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
const roomMessage = new RefreshRoomPromptMessage();
|
const roomMessage = new RefreshRoomPromptMessage();
|
||||||
roomMessage.setRoomid(roomId);
|
roomMessage.setRoomid(roomId);
|
||||||
@ -57,12 +59,10 @@ export class AdminController extends BaseController{
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.writeStatus("200");
|
res.writeStatus("200");
|
||||||
res.end('ok');
|
res.end("ok");
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
receiveGlobalMessagePrompt() {
|
receiveGlobalMessagePrompt() {
|
||||||
this.App.options("/message", (res: HttpResponse, req: HttpRequest) => {
|
this.App.options("/message", (res: HttpResponse, req: HttpRequest) => {
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
@ -71,59 +71,57 @@ export class AdminController extends BaseController{
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
this.App.post("/message", async (res: HttpResponse, req: HttpRequest) => {
|
this.App.post("/message", async (res: HttpResponse, req: HttpRequest) => {
|
||||||
|
|
||||||
res.onAborted(() => {
|
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();
|
const body = await res.json();
|
||||||
|
|
||||||
if (token !== ADMIN_API_TOKEN) {
|
if (token !== ADMIN_API_TOKEN) {
|
||||||
console.error('Admin access refused for token: '+token)
|
console.error("Admin access refused for token: " + token);
|
||||||
res.writeStatus("401 Unauthorized").end('Incorrect token');
|
res.writeStatus("401 Unauthorized").end("Incorrect token");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (typeof body.text !== 'string') {
|
if (typeof body.text !== "string") {
|
||||||
throw 'Incorrect text parameter'
|
throw "Incorrect text parameter";
|
||||||
}
|
}
|
||||||
if (body.type !== 'capacity' && body.type !== 'message') {
|
if (body.type !== "capacity" && body.type !== "message") {
|
||||||
throw 'Incorrect type parameter'
|
throw "Incorrect type parameter";
|
||||||
}
|
}
|
||||||
if (!body.targets || typeof body.targets !== 'object') {
|
if (!body.targets || typeof body.targets !== "object") {
|
||||||
throw 'Incorrect targets parameter'
|
throw "Incorrect targets parameter";
|
||||||
}
|
}
|
||||||
const text: string = body.text;
|
const text: string = body.text;
|
||||||
const type: string = body.type;
|
const type: string = body.type;
|
||||||
const targets: string[] = body.targets;
|
const targets: string[] = body.targets;
|
||||||
|
|
||||||
await Promise.all(targets.map((roomId) => {
|
await Promise.all(
|
||||||
return apiClientRepository.getClient(roomId).then((roomClient) =>{
|
targets.map((roomId) => {
|
||||||
return new Promise((res, rej) => {
|
return apiClientRepository.getClient(roomId).then((roomClient) => {
|
||||||
if (type === 'message') {
|
return new Promise((res, rej) => {
|
||||||
const roomMessage = new AdminRoomMessage();
|
if (type === "message") {
|
||||||
roomMessage.setMessage(text);
|
const roomMessage = new AdminRoomMessage();
|
||||||
roomMessage.setRoomid(roomId);
|
roomMessage.setMessage(text);
|
||||||
|
roomMessage.setRoomid(roomId);
|
||||||
|
|
||||||
roomClient.sendAdminMessageToRoom(roomMessage, (err) => {
|
roomClient.sendAdminMessageToRoom(roomMessage, (err) => {
|
||||||
err ? rej(err) : res();
|
err ? rej(err) : res();
|
||||||
});
|
});
|
||||||
} else if (type === 'capacity') {
|
} else if (type === "capacity") {
|
||||||
const roomMessage = new WorldFullWarningToRoomMessage();
|
const roomMessage = new WorldFullWarningToRoomMessage();
|
||||||
roomMessage.setRoomid(roomId);
|
roomMessage.setRoomid(roomId);
|
||||||
|
|
||||||
roomClient.sendWorldFullWarningToRoom(roomMessage, (err) => {
|
|
||||||
err ? rej(err) : res();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
roomClient.sendWorldFullWarningToRoom(roomMessage, (err) => {
|
||||||
|
err ? rej(err) : res();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
})
|
||||||
}));
|
);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.errorToResponse(err, res);
|
this.errorToResponse(err, res);
|
||||||
return;
|
return;
|
||||||
@ -131,7 +129,7 @@ export class AdminController extends BaseController{
|
|||||||
|
|
||||||
res.writeStatus("200");
|
res.writeStatus("200");
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
res.end('ok');
|
res.end("ok");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
import { v4 } from 'uuid';
|
import { v4 } from "uuid";
|
||||||
import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js";
|
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
||||||
import {BaseController} from "./BaseController";
|
import { BaseController } from "./BaseController";
|
||||||
import {adminApi} from "../Services/AdminApi";
|
import { adminApi } from "../Services/AdminApi";
|
||||||
import {jwtTokenManager} from "../Services/JWTTokenManager";
|
import { jwtTokenManager } from "../Services/JWTTokenManager";
|
||||||
import {parse} from "query-string";
|
import { parse } from "query-string";
|
||||||
|
|
||||||
export interface TokenInterface {
|
export interface TokenInterface {
|
||||||
userUuid: string
|
userUuid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AuthenticateController extends BaseController {
|
export class AuthenticateController extends BaseController {
|
||||||
|
constructor(private App: TemplatedApp) {
|
||||||
constructor(private App : TemplatedApp) {
|
|
||||||
super();
|
super();
|
||||||
this.register();
|
this.register();
|
||||||
this.verify();
|
this.verify();
|
||||||
@ -19,7 +18,7 @@ export class AuthenticateController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Try to login with an admin token
|
//Try to login with an admin token
|
||||||
private register(){
|
private register() {
|
||||||
this.App.options("/register", (res: HttpResponse, req: HttpRequest) => {
|
this.App.options("/register", (res: HttpResponse, req: HttpRequest) => {
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
|
|
||||||
@ -29,15 +28,15 @@ export class AuthenticateController extends BaseController {
|
|||||||
this.App.post("/register", (res: HttpResponse, req: HttpRequest) => {
|
this.App.post("/register", (res: HttpResponse, req: HttpRequest) => {
|
||||||
(async () => {
|
(async () => {
|
||||||
res.onAborted(() => {
|
res.onAborted(() => {
|
||||||
console.warn('Login request was aborted');
|
console.warn("Login request was aborted");
|
||||||
})
|
});
|
||||||
const param = await res.json();
|
const param = await res.json();
|
||||||
|
|
||||||
//todo: what to do if the organizationMemberToken is already used?
|
//todo: what to do if the organizationMemberToken is already used?
|
||||||
const organizationMemberToken:string|null = param.organizationMemberToken;
|
const organizationMemberToken: string | null = param.organizationMemberToken;
|
||||||
|
|
||||||
try {
|
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 data = await adminApi.fetchMemberDataByToken(organizationMemberToken);
|
||||||
const userUuid = data.userUuid;
|
const userUuid = data.userUuid;
|
||||||
const organizationSlug = data.organizationSlug;
|
const organizationSlug = data.organizationSlug;
|
||||||
@ -49,28 +48,26 @@ export class AuthenticateController extends BaseController {
|
|||||||
const authToken = jwtTokenManager.createJWTToken(userUuid);
|
const authToken = jwtTokenManager.createJWTToken(userUuid);
|
||||||
res.writeStatus("200 OK");
|
res.writeStatus("200 OK");
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
res.end(JSON.stringify({
|
res.end(
|
||||||
authToken,
|
JSON.stringify({
|
||||||
userUuid,
|
authToken,
|
||||||
organizationSlug,
|
userUuid,
|
||||||
worldSlug,
|
organizationSlug,
|
||||||
roomSlug,
|
worldSlug,
|
||||||
mapUrlStart,
|
roomSlug,
|
||||||
organizationMemberToken,
|
mapUrlStart,
|
||||||
textures
|
organizationMemberToken,
|
||||||
}));
|
textures,
|
||||||
|
})
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.errorToResponse(e, res);
|
this.errorToResponse(e, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
})();
|
})();
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private verify(){
|
private verify() {
|
||||||
this.App.options("/verify", (res: HttpResponse, req: HttpRequest) => {
|
this.App.options("/verify", (res: HttpResponse, req: HttpRequest) => {
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
|
|
||||||
@ -82,50 +79,55 @@ export class AuthenticateController extends BaseController {
|
|||||||
const query = parse(req.getQuery());
|
const query = parse(req.getQuery());
|
||||||
|
|
||||||
res.onAborted(() => {
|
res.onAborted(() => {
|
||||||
console.warn('verify request was aborted');
|
console.warn("verify request was aborted");
|
||||||
})
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await jwtTokenManager.getUserUuidFromToken(query.token as string);
|
await jwtTokenManager.getUserUuidFromToken(query.token as string);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
res.writeStatus("400 Bad Request");
|
res.writeStatus("400 Bad Request");
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
res.end(JSON.stringify({
|
res.end(
|
||||||
"success": false,
|
JSON.stringify({
|
||||||
"message": "Invalid JWT token"
|
success: false,
|
||||||
}));
|
message: "Invalid JWT token",
|
||||||
|
})
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
res.writeStatus("200 OK");
|
res.writeStatus("200 OK");
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
res.end(JSON.stringify({
|
res.end(
|
||||||
"success": true
|
JSON.stringify({
|
||||||
}));
|
success: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
})();
|
})();
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//permit to login on application. Return token to connect on Websocket IO.
|
//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.App.options("/anonymLogin", (res: HttpResponse, req: HttpRequest) => {
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.App.post("/anonymLogin", (res: HttpResponse, req: HttpRequest) => {
|
this.App.post("/anonymLogin", (res: HttpResponse, req: HttpRequest) => {
|
||||||
res.onAborted(() => {
|
res.onAborted(() => {
|
||||||
console.warn('Login request was aborted');
|
console.warn("Login request was aborted");
|
||||||
})
|
});
|
||||||
|
|
||||||
const userUuid = v4();
|
const userUuid = v4();
|
||||||
const authToken = jwtTokenManager.createJWTToken(userUuid);
|
const authToken = jwtTokenManager.createJWTToken(userUuid);
|
||||||
res.writeStatus("200 OK");
|
res.writeStatus("200 OK");
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
res.end(JSON.stringify({
|
res.end(
|
||||||
authToken,
|
JSON.stringify({
|
||||||
userUuid,
|
authToken,
|
||||||
}));
|
userUuid,
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import {HttpResponse} from "uWebSockets.js";
|
import { HttpResponse } from "uWebSockets.js";
|
||||||
|
|
||||||
|
|
||||||
export class BaseController {
|
export class BaseController {
|
||||||
protected addCorsHeaders(res: HttpResponse): void {
|
protected addCorsHeaders(res: HttpResponse): void {
|
||||||
res.writeHeader('access-control-allow-headers', 'Origin, X-Requested-With, Content-Type, Accept');
|
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-methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
|
||||||
res.writeHeader('access-control-allow-origin', '*');
|
res.writeHeader("access-control-allow-origin", "*");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -16,23 +15,23 @@ export class BaseController {
|
|||||||
if (e && e.message) {
|
if (e && e.message) {
|
||||||
let url = e?.config?.url;
|
let url = e?.config?.url;
|
||||||
if (url !== undefined) {
|
if (url !== undefined) {
|
||||||
url = ' for URL: '+url;
|
url = " for URL: " + url;
|
||||||
} else {
|
} else {
|
||||||
url = '';
|
url = "";
|
||||||
}
|
}
|
||||||
console.error('ERROR: '+e.message+url);
|
console.error("ERROR: " + e.message + url);
|
||||||
} else if (typeof(e) === 'string') {
|
} else if (typeof e === "string") {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
if (e.stack) {
|
if (e.stack) {
|
||||||
console.error(e.stack);
|
console.error(e.stack);
|
||||||
}
|
}
|
||||||
if (e.response) {
|
if (e.response) {
|
||||||
res.writeStatus(e.response.status+" "+e.response.statusText);
|
res.writeStatus(e.response.status + " " + e.response.statusText);
|
||||||
this.addCorsHeaders(res);
|
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 {
|
} else {
|
||||||
res.writeStatus("500 Internal Server Error")
|
res.writeStatus("500 Internal Server Error");
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
res.end("An error occurred");
|
res.end("An error occurred");
|
||||||
}
|
}
|
||||||
|
@ -1,45 +1,46 @@
|
|||||||
import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_TOKEN } from "../Enum/EnvironmentVariable";
|
||||||
import {IoSocketController} from "_Controller/IoSocketController";
|
import { IoSocketController } from "_Controller/IoSocketController";
|
||||||
import {stringify} from "circular-json";
|
import { stringify } from "circular-json";
|
||||||
import {HttpRequest, HttpResponse} from "uWebSockets.js";
|
import { HttpRequest, HttpResponse } from "uWebSockets.js";
|
||||||
import { parse } from 'query-string';
|
import { parse } from "query-string";
|
||||||
import {App} from "../Server/sifrr.server";
|
import { App } from "../Server/sifrr.server";
|
||||||
import {socketManager} from "../Services/SocketManager";
|
import { socketManager } from "../Services/SocketManager";
|
||||||
|
|
||||||
export class DebugController {
|
export class DebugController {
|
||||||
constructor(private App : App) {
|
constructor(private App: App) {
|
||||||
this.getDump();
|
this.getDump();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDump() {
|
||||||
getDump(){
|
|
||||||
this.App.get("/dump", (res: HttpResponse, req: HttpRequest) => {
|
this.App.get("/dump", (res: HttpResponse, req: HttpRequest) => {
|
||||||
const query = parse(req.getQuery());
|
const query = parse(req.getQuery());
|
||||||
|
|
||||||
if (query.token !== ADMIN_API_TOKEN) {
|
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(
|
return res
|
||||||
socketManager.getWorlds(),
|
.writeStatus("200 OK")
|
||||||
(key: unknown, value: unknown) => {
|
.writeHeader("Content-Type", "application/json")
|
||||||
if(value instanceof Map) {
|
.end(
|
||||||
const obj: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any
|
stringify(socketManager.getWorlds(), (key: unknown, value: unknown) => {
|
||||||
for (const [mapKey, mapValue] of value.entries()) {
|
if (value instanceof Map) {
|
||||||
obj[mapKey] = mapValue;
|
const obj: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
}
|
for (const [mapKey, mapValue] of value.entries()) {
|
||||||
return obj;
|
obj[mapKey] = mapValue;
|
||||||
} else if(value instanceof Set) {
|
}
|
||||||
|
return obj;
|
||||||
|
} else if (value instanceof Set) {
|
||||||
const obj: Array<unknown> = [];
|
const obj: Array<unknown> = [];
|
||||||
for (const [setKey, setValue] of value.entries()) {
|
for (const [setKey, setValue] of value.entries()) {
|
||||||
obj.push(setValue);
|
obj.push(setValue);
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
));
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {CharacterLayer, ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.."
|
import { CharacterLayer, ExSocketInterface } from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.."
|
||||||
import {GameRoomPolicyTypes} from "../Model/PusherRoom";
|
import { GameRoomPolicyTypes } from "../Model/PusherRoom";
|
||||||
import {PointInterface} from "../Model/Websocket/PointInterface";
|
import { PointInterface } from "../Model/Websocket/PointInterface";
|
||||||
import {
|
import {
|
||||||
SetPlayerDetailsMessage,
|
SetPlayerDetailsMessage,
|
||||||
SubMessage,
|
SubMessage,
|
||||||
@ -18,17 +18,17 @@ import {
|
|||||||
CompanionMessage,
|
CompanionMessage,
|
||||||
EmotePromptMessage,
|
EmotePromptMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import {UserMovesMessage} from "../Messages/generated/messages_pb";
|
import { UserMovesMessage } from "../Messages/generated/messages_pb";
|
||||||
import {TemplatedApp} from "uWebSockets.js"
|
import { TemplatedApp } from "uWebSockets.js";
|
||||||
import {parse} from "query-string";
|
import { parse } from "query-string";
|
||||||
import {jwtTokenManager} from "../Services/JWTTokenManager";
|
import { jwtTokenManager } from "../Services/JWTTokenManager";
|
||||||
import {adminApi, CharacterTexture, FetchMemberDataByUuidResponse} from "../Services/AdminApi";
|
import { adminApi, CharacterTexture, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
||||||
import {SocketManager, socketManager} from "../Services/SocketManager";
|
import { SocketManager, socketManager } from "../Services/SocketManager";
|
||||||
import {emitInBatch} from "../Services/IoSocketHelpers";
|
import { emitInBatch } from "../Services/IoSocketHelpers";
|
||||||
import {ADMIN_API_TOKEN, ADMIN_API_URL, SOCKET_IDLE_TIMER} from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_TOKEN, ADMIN_API_URL, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable";
|
||||||
import {Zone} from "_Model/Zone";
|
import { Zone } from "_Model/Zone";
|
||||||
import {ExAdminSocketInterface} from "_Model/Websocket/ExAdminSocketInterface";
|
import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface";
|
||||||
import {v4} from "uuid";
|
import { v4 } from "uuid";
|
||||||
|
|
||||||
export class IoSocketController {
|
export class IoSocketController {
|
||||||
private nextUserId: number = 1;
|
private nextUserId: number = 1;
|
||||||
@ -39,32 +39,29 @@ export class IoSocketController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
adminRoomSocket() {
|
adminRoomSocket() {
|
||||||
this.app.ws('/admin/rooms', {
|
this.app.ws("/admin/rooms", {
|
||||||
upgrade: (res, req, context) => {
|
upgrade: (res, req, context) => {
|
||||||
const query = parse(req.getQuery());
|
const query = parse(req.getQuery());
|
||||||
const websocketKey = req.getHeader('sec-websocket-key');
|
const websocketKey = req.getHeader("sec-websocket-key");
|
||||||
const websocketProtocol = req.getHeader('sec-websocket-protocol');
|
const websocketProtocol = req.getHeader("sec-websocket-protocol");
|
||||||
const websocketExtensions = req.getHeader('sec-websocket-extensions');
|
const websocketExtensions = req.getHeader("sec-websocket-extensions");
|
||||||
const token = query.token;
|
const token = query.token;
|
||||||
if (token !== ADMIN_API_TOKEN) {
|
if (token !== ADMIN_API_TOKEN) {
|
||||||
console.log('Admin access refused for token: '+token)
|
console.log("Admin access refused for token: " + token);
|
||||||
res.writeStatus("401 Unauthorized").end('Incorrect token');
|
res.writeStatus("401 Unauthorized").end("Incorrect token");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const roomId = query.roomId;
|
const roomId = query.roomId;
|
||||||
if (typeof roomId !== 'string') {
|
if (typeof roomId !== "string") {
|
||||||
console.error('Received')
|
console.error("Received");
|
||||||
res.writeStatus("400 Bad Request").end('Missing room id');
|
res.writeStatus("400 Bad Request").end("Missing room id");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.upgrade(
|
res.upgrade({ roomId }, websocketKey, websocketProtocol, websocketExtensions, context);
|
||||||
{roomId},
|
|
||||||
websocketKey, websocketProtocol, websocketExtensions, context,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
open: (ws) => {
|
open: (ws) => {
|
||||||
console.log('Admin socket connect for room: '+ws.roomId);
|
console.log("Admin socket connect for room: " + ws.roomId);
|
||||||
ws.disconnecting = false;
|
ws.disconnecting = false;
|
||||||
|
|
||||||
socketManager.handleAdminRoom(ws as ExAdminSocketInterface, ws.roomId as string);
|
socketManager.handleAdminRoom(ws as ExAdminSocketInterface, ws.roomId as string);
|
||||||
@ -74,24 +71,34 @@ export class IoSocketController {
|
|||||||
const roomId = ws.roomId as string;
|
const roomId = ws.roomId as string;
|
||||||
|
|
||||||
//TODO refactor message type and data
|
//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)));
|
JSON.parse(new TextDecoder("utf-8").decode(new Uint8Array(arrayBuffer)));
|
||||||
|
|
||||||
if(message.event === 'user-message') {
|
if (message.event === "user-message") {
|
||||||
const messageToEmit = (message.message as { message: string, type: string, userUuid: string });
|
const messageToEmit = message.message as { message: string; type: string; userUuid: string };
|
||||||
if(messageToEmit.type === 'banned'){
|
if (messageToEmit.type === "banned") {
|
||||||
socketManager.emitBan(messageToEmit.userUuid, messageToEmit.message, messageToEmit.type, ws.roomId as string);
|
socketManager.emitBan(
|
||||||
|
messageToEmit.userUuid,
|
||||||
|
messageToEmit.message,
|
||||||
|
messageToEmit.type,
|
||||||
|
ws.roomId as string
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if(messageToEmit.type === 'ban') {
|
if (messageToEmit.type === "ban") {
|
||||||
socketManager.emitSendUserMessage(messageToEmit.userUuid, messageToEmit.message, messageToEmit.type, ws.roomId as string);
|
socketManager.emitSendUserMessage(
|
||||||
|
messageToEmit.userUuid,
|
||||||
|
messageToEmit.message,
|
||||||
|
messageToEmit.type,
|
||||||
|
ws.roomId as string
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
close: (ws, code, message) => {
|
close: (ws, code, message) => {
|
||||||
const Client = (ws as ExAdminSocketInterface);
|
const Client = ws as ExAdminSocketInterface;
|
||||||
try {
|
try {
|
||||||
Client.disconnecting = true;
|
Client.disconnecting = true;
|
||||||
socketManager.leaveAdminRoom(Client);
|
socketManager.leaveAdminRoom(Client);
|
||||||
@ -99,12 +106,12 @@ export class IoSocketController {
|
|||||||
console.error('An error occurred on admin "disconnect"');
|
console.error('An error occurred on admin "disconnect"');
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ioConnection() {
|
ioConnection() {
|
||||||
this.app.ws('/room', {
|
this.app.ws("/room", {
|
||||||
/* Options */
|
/* Options */
|
||||||
//compression: uWS.SHARED_COMPRESSOR,
|
//compression: uWS.SHARED_COMPRESSOR,
|
||||||
idleTimeout: SOCKET_IDLE_TIMER,
|
idleTimeout: SOCKET_IDLE_TIMER,
|
||||||
@ -114,7 +121,7 @@ export class IoSocketController {
|
|||||||
upgrade: (res, req, context) => {
|
upgrade: (res, req, context) => {
|
||||||
(async () => {
|
(async () => {
|
||||||
/* Keep track of abortions */
|
/* Keep track of abortions */
|
||||||
const upgradeAborted = {aborted: false};
|
const upgradeAborted = { aborted: false };
|
||||||
|
|
||||||
res.onAborted(() => {
|
res.onAborted(() => {
|
||||||
/* We can simply signal that we were aborted */
|
/* We can simply signal that we were aborted */
|
||||||
@ -123,15 +130,15 @@ export class IoSocketController {
|
|||||||
|
|
||||||
const url = req.getUrl();
|
const url = req.getUrl();
|
||||||
const query = parse(req.getQuery());
|
const query = parse(req.getQuery());
|
||||||
const websocketKey = req.getHeader('sec-websocket-key');
|
const websocketKey = req.getHeader("sec-websocket-key");
|
||||||
const websocketProtocol = req.getHeader('sec-websocket-protocol');
|
const websocketProtocol = req.getHeader("sec-websocket-protocol");
|
||||||
const websocketExtensions = req.getHeader('sec-websocket-extensions');
|
const websocketExtensions = req.getHeader("sec-websocket-extensions");
|
||||||
const IPAddress = req.getHeader('x-forwarded-for');
|
const IPAddress = req.getHeader("x-forwarded-for");
|
||||||
|
|
||||||
const roomId = query.roomId;
|
const roomId = query.roomId;
|
||||||
try {
|
try {
|
||||||
if (typeof roomId !== 'string') {
|
if (typeof roomId !== "string") {
|
||||||
throw new Error('Undefined room ID: ');
|
throw new Error("Undefined room ID: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = query.token;
|
const token = query.token;
|
||||||
@ -143,62 +150,69 @@ export class IoSocketController {
|
|||||||
const right = Number(query.right);
|
const right = Number(query.right);
|
||||||
const name = query.name;
|
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 = new CompanionMessage();
|
||||||
companion.setName(query.companion);
|
companion.setName(query.companion);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof name !== 'string') {
|
if (typeof name !== "string") {
|
||||||
throw new Error('Expecting name');
|
throw new Error("Expecting name");
|
||||||
}
|
}
|
||||||
if (name === '') {
|
if (name === "") {
|
||||||
throw new Error('No empty name');
|
throw new Error("No empty name");
|
||||||
}
|
}
|
||||||
let characterLayers = query.characterLayers;
|
let characterLayers = query.characterLayers;
|
||||||
if (characterLayers === null) {
|
if (characterLayers === null) {
|
||||||
throw new Error('Expecting skin');
|
throw new Error("Expecting skin");
|
||||||
}
|
}
|
||||||
if (typeof characterLayers === 'string') {
|
if (typeof characterLayers === "string") {
|
||||||
characterLayers = [ characterLayers ];
|
characterLayers = [characterLayers];
|
||||||
}
|
}
|
||||||
|
|
||||||
const userUuid = await jwtTokenManager.getUserUuidFromToken(token, IPAddress, roomId);
|
const userUuid = await jwtTokenManager.getUserUuidFromToken(token, IPAddress, roomId);
|
||||||
|
|
||||||
let memberTags: string[] = [];
|
let memberTags: string[] = [];
|
||||||
let memberVisitCardUrl: string|null = null;
|
let memberVisitCardUrl: string | null = null;
|
||||||
let memberMessages: unknown;
|
let memberMessages: unknown;
|
||||||
let memberTextures: CharacterTexture[] = [];
|
let memberTextures: CharacterTexture[] = [];
|
||||||
const room = await socketManager.getOrCreateRoom(roomId);
|
const room = await socketManager.getOrCreateRoom(roomId);
|
||||||
if (ADMIN_API_URL) {
|
if (ADMIN_API_URL) {
|
||||||
try {
|
try {
|
||||||
let userData : FetchMemberDataByUuidResponse = {
|
let userData: FetchMemberDataByUuidResponse = {
|
||||||
uuid: v4(),
|
uuid: v4(),
|
||||||
tags: [],
|
tags: [],
|
||||||
visitCardUrl: null,
|
visitCardUrl: null,
|
||||||
textures: [],
|
textures: [],
|
||||||
messages: [],
|
messages: [],
|
||||||
anonymous: true
|
anonymous: true,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
userData = await adminApi.fetchMemberDataByUuid(userUuid, roomId);
|
userData = await adminApi.fetchMemberDataByUuid(userUuid, roomId);
|
||||||
}catch (err){
|
} catch (err) {
|
||||||
if (err?.response?.status == 404) {
|
if (err?.response?.status == 404) {
|
||||||
// If we get an HTTP 404, the token is invalid. Let's perform an anonymous login!
|
// 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.');
|
console.warn(
|
||||||
} else if(err?.response?.status == 403) {
|
'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.
|
// 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.
|
// we finish immediately the upgrade then we will close the socket as soon as it starts opening.
|
||||||
return res.upgrade({
|
return res.upgrade(
|
||||||
rejected: true,
|
{
|
||||||
message: err?.response?.data.message,
|
rejected: true,
|
||||||
status: err?.response?.status
|
message: err?.response?.data.message,
|
||||||
}, websocketKey,
|
status: err?.response?.status,
|
||||||
websocketProtocol,
|
},
|
||||||
websocketExtensions,
|
websocketKey,
|
||||||
context);
|
websocketProtocol,
|
||||||
}else{
|
websocketExtensions,
|
||||||
|
context
|
||||||
|
);
|
||||||
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,21 +220,30 @@ export class IoSocketController {
|
|||||||
memberTags = userData.tags;
|
memberTags = userData.tags;
|
||||||
memberVisitCardUrl = userData.visitCardUrl;
|
memberVisitCardUrl = userData.visitCardUrl;
|
||||||
memberTextures = userData.textures;
|
memberTextures = userData.textures;
|
||||||
if (!room.public && room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && (userData.anonymous === true || !room.canAccess(memberTags))) {
|
if (
|
||||||
throw new Error('Insufficient privileges to access this room')
|
!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) {
|
if (
|
||||||
throw new Error('Use the login URL to connect')
|
!room.public &&
|
||||||
|
room.policyType === GameRoomPolicyTypes.MEMBERS_ONLY_POLICY &&
|
||||||
|
userData.anonymous === true
|
||||||
|
) {
|
||||||
|
throw new Error("Use the login URL to connect");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} 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);
|
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[]
|
// Generate characterLayers objects from characterLayers string[]
|
||||||
const characterLayerObjs: CharacterLayer[] = SocketManager.mergeCharacterLayersAndCustomTextures(characterLayers, memberTextures);
|
const characterLayerObjs: CharacterLayer[] =
|
||||||
|
SocketManager.mergeCharacterLayersAndCustomTextures(characterLayers, memberTextures);
|
||||||
|
|
||||||
if (upgradeAborted.aborted) {
|
if (upgradeAborted.aborted) {
|
||||||
console.log("Ouch! Client disconnected before we could upgrade it!");
|
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 */
|
/* 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.
|
// Data passed here is accessible on the "websocket" socket object.
|
||||||
url,
|
url,
|
||||||
token,
|
token,
|
||||||
@ -246,22 +270,22 @@ export class IoSocketController {
|
|||||||
position: {
|
position: {
|
||||||
x: x,
|
x: x,
|
||||||
y: y,
|
y: y,
|
||||||
direction: 'down',
|
direction: "down",
|
||||||
moving: false
|
moving: false,
|
||||||
} as PointInterface,
|
} as PointInterface,
|
||||||
viewport: {
|
viewport: {
|
||||||
top,
|
top,
|
||||||
right,
|
right,
|
||||||
bottom,
|
bottom,
|
||||||
left
|
left,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
/* Spell these correctly */
|
/* Spell these correctly */
|
||||||
websocketKey,
|
websocketKey,
|
||||||
websocketProtocol,
|
websocketProtocol,
|
||||||
websocketExtensions,
|
websocketExtensions,
|
||||||
context);
|
context
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
/*if (e instanceof Error) {
|
/*if (e instanceof Error) {
|
||||||
console.log(e.message);
|
console.log(e.message);
|
||||||
@ -269,23 +293,26 @@ export class IoSocketController {
|
|||||||
} else {
|
} else {
|
||||||
res.writeStatus("500 Internal Server Error").end('An error occurred');
|
res.writeStatus("500 Internal Server Error").end('An error occurred');
|
||||||
}*/
|
}*/
|
||||||
return res.upgrade({
|
return res.upgrade(
|
||||||
rejected: true,
|
{
|
||||||
message: e.message ? e.message : '500 Internal Server Error'
|
rejected: true,
|
||||||
}, websocketKey,
|
message: e.message ? e.message : "500 Internal Server Error",
|
||||||
websocketProtocol,
|
},
|
||||||
websocketExtensions,
|
websocketKey,
|
||||||
context);
|
websocketProtocol,
|
||||||
|
websocketExtensions,
|
||||||
|
context
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
},
|
},
|
||||||
/* Handlers */
|
/* Handlers */
|
||||||
open: (ws) => {
|
open: (ws) => {
|
||||||
if(ws.rejected === true) {
|
if (ws.rejected === true) {
|
||||||
//FIX ME to use status code
|
//FIX ME to use status code
|
||||||
if(ws.message === 'World is full'){
|
if (ws.message === "World is full") {
|
||||||
socketManager.emitWorldFullMessage(ws);
|
socketManager.emitWorldFullMessage(ws);
|
||||||
}else{
|
} else {
|
||||||
socketManager.emitConnexionErrorMessage(ws, ws.message as string);
|
socketManager.emitConnexionErrorMessage(ws, ws.message as string);
|
||||||
}
|
}
|
||||||
ws.close();
|
ws.close();
|
||||||
@ -299,7 +326,7 @@ export class IoSocketController {
|
|||||||
//get data information and show messages
|
//get data information and show messages
|
||||||
if (client.messages && Array.isArray(client.messages)) {
|
if (client.messages && Array.isArray(client.messages)) {
|
||||||
client.messages.forEach((c: unknown) => {
|
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();
|
const sendUserMessage = new SendUserMessage();
|
||||||
sendUserMessage.setType(messageToSend.type);
|
sendUserMessage.setType(messageToSend.type);
|
||||||
@ -323,33 +350,48 @@ export class IoSocketController {
|
|||||||
} else if (message.hasUsermovesmessage()) {
|
} else if (message.hasUsermovesmessage()) {
|
||||||
socketManager.handleUserMovesMessage(client, message.getUsermovesmessage() as UserMovesMessage);
|
socketManager.handleUserMovesMessage(client, message.getUsermovesmessage() as UserMovesMessage);
|
||||||
} else if (message.hasSetplayerdetailsmessage()) {
|
} else if (message.hasSetplayerdetailsmessage()) {
|
||||||
socketManager.handleSetPlayerDetails(client, message.getSetplayerdetailsmessage() as SetPlayerDetailsMessage);
|
socketManager.handleSetPlayerDetails(
|
||||||
|
client,
|
||||||
|
message.getSetplayerdetailsmessage() as SetPlayerDetailsMessage
|
||||||
|
);
|
||||||
} else if (message.hasSilentmessage()) {
|
} else if (message.hasSilentmessage()) {
|
||||||
socketManager.handleSilentMessage(client, message.getSilentmessage() as SilentMessage);
|
socketManager.handleSilentMessage(client, message.getSilentmessage() as SilentMessage);
|
||||||
} else if (message.hasItemeventmessage()) {
|
} else if (message.hasItemeventmessage()) {
|
||||||
socketManager.handleItemEvent(client, message.getItemeventmessage() as ItemEventMessage);
|
socketManager.handleItemEvent(client, message.getItemeventmessage() as ItemEventMessage);
|
||||||
} else if (message.hasWebrtcsignaltoservermessage()) {
|
} else if (message.hasWebrtcsignaltoservermessage()) {
|
||||||
socketManager.emitVideo(client, message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage);
|
socketManager.emitVideo(
|
||||||
|
client,
|
||||||
|
message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage
|
||||||
|
);
|
||||||
} else if (message.hasWebrtcscreensharingsignaltoservermessage()) {
|
} else if (message.hasWebrtcscreensharingsignaltoservermessage()) {
|
||||||
socketManager.emitScreenSharing(client, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage);
|
socketManager.emitScreenSharing(
|
||||||
|
client,
|
||||||
|
message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage
|
||||||
|
);
|
||||||
} else if (message.hasPlayglobalmessage()) {
|
} else if (message.hasPlayglobalmessage()) {
|
||||||
socketManager.emitPlayGlobalMessage(client, message.getPlayglobalmessage() as PlayGlobalMessage);
|
socketManager.emitPlayGlobalMessage(client, message.getPlayglobalmessage() as PlayGlobalMessage);
|
||||||
} else if (message.hasReportplayermessage()){
|
} else if (message.hasReportplayermessage()) {
|
||||||
socketManager.handleReportMessage(client, message.getReportplayermessage() as ReportPlayerMessage);
|
socketManager.handleReportMessage(client, message.getReportplayermessage() as ReportPlayerMessage);
|
||||||
} else if (message.hasQueryjitsijwtmessage()){
|
} else if (message.hasQueryjitsijwtmessage()) {
|
||||||
socketManager.handleQueryJitsiJwtMessage(client, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
|
socketManager.handleQueryJitsiJwtMessage(
|
||||||
} else if (message.hasEmotepromptmessage()){
|
client,
|
||||||
socketManager.handleEmotePromptMessage(client, message.getEmotepromptmessage() as EmotePromptMessage);
|
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);
|
//let ok = ws.send(message, isBinary);
|
||||||
},
|
},
|
||||||
drain: (ws) => {
|
drain: (ws) => {
|
||||||
console.log('WebSocket backpressure: ' + ws.getBufferedAmount());
|
console.log("WebSocket backpressure: " + ws.getBufferedAmount());
|
||||||
},
|
},
|
||||||
close: (ws, code, message) => {
|
close: (ws, code, message) => {
|
||||||
const Client = (ws as ExSocketInterface);
|
const Client = ws as ExSocketInterface;
|
||||||
try {
|
try {
|
||||||
Client.disconnecting = true;
|
Client.disconnecting = true;
|
||||||
//leave room
|
//leave room
|
||||||
@ -358,13 +400,13 @@ export class IoSocketController {
|
|||||||
console.error('An error occurred on "disconnect"');
|
console.error('An error occurred on "disconnect"');
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
private initClient(ws: any): ExSocketInterface {
|
private initClient(ws: any): ExSocketInterface {
|
||||||
const client : ExSocketInterface = ws;
|
const client: ExSocketInterface = ws;
|
||||||
client.userId = this.nextUserId;
|
client.userId = this.nextUserId;
|
||||||
this.nextUserId++;
|
this.nextUserId++;
|
||||||
client.userUuid = ws.userUuid;
|
client.userUuid = ws.userUuid;
|
||||||
@ -374,7 +416,7 @@ export class IoSocketController {
|
|||||||
client.batchTimeout = null;
|
client.batchTimeout = null;
|
||||||
client.emitInBatch = (payload: SubMessage): void => {
|
client.emitInBatch = (payload: SubMessage): void => {
|
||||||
emitInBatch(client, payload);
|
emitInBatch(client, payload);
|
||||||
}
|
};
|
||||||
client.disconnecting = false;
|
client.disconnecting = false;
|
||||||
|
|
||||||
client.messages = ws.messages;
|
client.messages = ws.messages;
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js";
|
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
||||||
import {BaseController} from "./BaseController";
|
import { BaseController } from "./BaseController";
|
||||||
import {parse} from "query-string";
|
import { parse } from "query-string";
|
||||||
import {adminApi} from "../Services/AdminApi";
|
import { adminApi } from "../Services/AdminApi";
|
||||||
|
|
||||||
|
export class MapController extends BaseController {
|
||||||
export class MapController extends BaseController{
|
constructor(private App: TemplatedApp) {
|
||||||
|
|
||||||
constructor(private App : TemplatedApp) {
|
|
||||||
super();
|
super();
|
||||||
this.App = App;
|
this.App = App;
|
||||||
this.getMapUrl();
|
this.getMapUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Returns a map mapping map name to file name of the map
|
// Returns a map mapping map name to file name of the map
|
||||||
getMapUrl() {
|
getMapUrl() {
|
||||||
this.App.options("/map", (res: HttpResponse, req: HttpRequest) => {
|
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) => {
|
this.App.get("/map", (res: HttpResponse, req: HttpRequest) => {
|
||||||
|
|
||||||
res.onAborted(() => {
|
res.onAborted(() => {
|
||||||
console.warn('/map request was aborted');
|
console.warn("/map request was aborted");
|
||||||
})
|
});
|
||||||
|
|
||||||
const query = parse(req.getQuery());
|
const query = parse(req.getQuery());
|
||||||
|
|
||||||
if (typeof query.organizationSlug !== 'string') {
|
if (typeof query.organizationSlug !== "string") {
|
||||||
console.error('Expected organizationSlug parameter');
|
console.error("Expected organizationSlug parameter");
|
||||||
res.writeStatus("400 Bad request");
|
res.writeStatus("400 Bad request");
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
res.end("Expected organizationSlug parameter");
|
res.end("Expected organizationSlug parameter");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (typeof query.worldSlug !== 'string') {
|
if (typeof query.worldSlug !== "string") {
|
||||||
console.error('Expected worldSlug parameter');
|
console.error("Expected worldSlug parameter");
|
||||||
res.writeStatus("400 Bad request");
|
res.writeStatus("400 Bad request");
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
res.end("Expected worldSlug parameter");
|
res.end("Expected worldSlug parameter");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (typeof query.roomSlug !== 'string' && query.roomSlug !== undefined) {
|
if (typeof query.roomSlug !== "string" && query.roomSlug !== undefined) {
|
||||||
console.error('Expected only one roomSlug parameter');
|
console.error("Expected only one roomSlug parameter");
|
||||||
res.writeStatus("400 Bad request");
|
res.writeStatus("400 Bad request");
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
res.end("Expected only one roomSlug parameter");
|
res.end("Expected only one roomSlug parameter");
|
||||||
@ -53,7 +49,11 @@ export class MapController extends BaseController{
|
|||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
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");
|
res.writeStatus("200 OK");
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
@ -62,7 +62,6 @@ export class MapController extends BaseController{
|
|||||||
this.errorToResponse(e, res);
|
this.errorToResponse(e, res);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {App} from "../Server/sifrr.server";
|
import { App } from "../Server/sifrr.server";
|
||||||
import {HttpRequest, HttpResponse} from "uWebSockets.js";
|
import { HttpRequest, HttpResponse } from "uWebSockets.js";
|
||||||
const register = require('prom-client').register;
|
const register = require("prom-client").register;
|
||||||
const collectDefaultMetrics = require('prom-client').collectDefaultMetrics;
|
const collectDefaultMetrics = require("prom-client").collectDefaultMetrics;
|
||||||
|
|
||||||
export class PrometheusController {
|
export class PrometheusController {
|
||||||
constructor(private App: App) {
|
constructor(private App: App) {
|
||||||
@ -14,7 +14,7 @@ export class PrometheusController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private metrics(res: HttpResponse, req: HttpRequest): void {
|
private metrics(res: HttpResponse, req: HttpRequest): void {
|
||||||
res.writeHeader('Content-Type', register.contentType);
|
res.writeHeader("Content-Type", register.contentType);
|
||||||
res.end(register.metrics());
|
res.end(register.metrics());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY";
|
const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY";
|
||||||
const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIMUM_DISTANCE) : 64;
|
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 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 ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == "true" : false;
|
||||||
const API_URL = process.env.API_URL || '';
|
const API_URL = process.env.API_URL || "";
|
||||||
const ADMIN_API_URL = process.env.ADMIN_API_URL || '';
|
const ADMIN_API_URL = process.env.ADMIN_API_URL || "";
|
||||||
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || 'myapitoken';
|
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "myapitoken";
|
||||||
const MAX_USERS_PER_ROOM = parseInt(process.env.MAX_USERS_PER_ROOM || '') || 600;
|
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 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_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
|
||||||
const JITSI_ISS = process.env.JITSI_ISS || '';
|
const JITSI_ISS = process.env.JITSI_ISS || "";
|
||||||
const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || '';
|
const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || "";
|
||||||
const PUSHER_HTTP_PORT = parseInt(process.env.PUSHER_HTTP_PORT || '8080') || 8080
|
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 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 {
|
export {
|
||||||
@ -26,5 +26,5 @@ export {
|
|||||||
JITSI_URL,
|
JITSI_URL,
|
||||||
JITSI_ISS,
|
JITSI_ISS,
|
||||||
SECRET_JITSI_KEY,
|
SECRET_JITSI_KEY,
|
||||||
PUSHER_HTTP_PORT
|
PUSHER_HTTP_PORT,
|
||||||
}
|
};
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import {PositionInterface} from "_Model/PositionInterface";
|
import { PositionInterface } from "_Model/PositionInterface";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A physical object that can be placed into a Zone
|
* A physical object that can be placed into a Zone
|
||||||
*/
|
*/
|
||||||
export interface Movable {
|
export interface Movable {
|
||||||
getPosition(): PositionInterface
|
getPosition(): PositionInterface;
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,9 @@
|
|||||||
* The PositionNotifier is important for performance. It allows us to send the position of players only to a restricted
|
* 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.
|
* number of players around the current player.
|
||||||
*/
|
*/
|
||||||
import {Zone, ZoneEventListener} from "./Zone";
|
import { Zone, ZoneEventListener } from "./Zone";
|
||||||
import {ViewportInterface} from "_Model/Websocket/ViewportMessage";
|
import { ViewportInterface } from "_Model/Websocket/ViewportMessage";
|
||||||
import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
|
import { ExSocketInterface } from "_Model/Websocket/ExSocketInterface";
|
||||||
//import Debug from "debug";
|
//import Debug from "debug";
|
||||||
|
|
||||||
//const debug = Debug('positiondispatcher');
|
//const debug = Debug('positiondispatcher');
|
||||||
@ -21,19 +21,22 @@ interface ZoneDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PositionDispatcher {
|
export class PositionDispatcher {
|
||||||
|
|
||||||
// TODO: we need a way to clean the zones if noone is in the zone and noone listening (to free memory!)
|
// TODO: we need a way to clean the zones if noone is in the zone and noone listening (to free memory!)
|
||||||
|
|
||||||
private zones: Zone[][] = [];
|
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 {
|
private getZoneDescriptorFromCoordinates(x: number, y: number): ZoneDescriptor {
|
||||||
return {
|
return {
|
||||||
i: Math.floor(x / this.zoneWidth),
|
i: Math.floor(x / this.zoneWidth),
|
||||||
j: Math.floor(y / this.zoneHeight),
|
j: Math.floor(y / this.zoneHeight),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -41,7 +44,7 @@ export class PositionDispatcher {
|
|||||||
*/
|
*/
|
||||||
public setViewport(socket: ExSocketInterface, viewport: ViewportInterface): void {
|
public setViewport(socket: ExSocketInterface, viewport: ViewportInterface): void {
|
||||||
if (viewport.left > viewport.right || viewport.top > viewport.bottom) {
|
if (viewport.left > viewport.right || viewport.top > viewport.bottom) {
|
||||||
console.warn('Invalid viewport received: ', viewport);
|
console.warn("Invalid viewport received: ", viewport);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,8 +60,8 @@ export class PositionDispatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const addedZones = [...newZones].filter(x => !oldZones.has(x));
|
const addedZones = [...newZones].filter((x) => !oldZones.has(x));
|
||||||
const removedZones = [...oldZones].filter(x => !newZones.has(x));
|
const removedZones = [...oldZones].filter((x) => !newZones.has(x));
|
||||||
|
|
||||||
for (const zone of addedZones) {
|
for (const zone of addedZones) {
|
||||||
zone.startListening(socket);
|
zone.startListening(socket);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export interface PositionInterface {
|
export interface PositionInterface {
|
||||||
x: number,
|
x: number;
|
||||||
y: number
|
y: number;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
|
import { ExSocketInterface } from "_Model/Websocket/ExSocketInterface";
|
||||||
import {PositionDispatcher} from "./PositionDispatcher";
|
import { PositionDispatcher } from "./PositionDispatcher";
|
||||||
import {ViewportInterface} from "_Model/Websocket/ViewportMessage";
|
import { ViewportInterface } from "_Model/Websocket/ViewportMessage";
|
||||||
import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "./RoomIdentifier";
|
import { extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous } from "./RoomIdentifier";
|
||||||
import {arrayIntersect} from "../Services/ArrayHelper";
|
import { arrayIntersect } from "../Services/ArrayHelper";
|
||||||
import {ZoneEventListener} from "_Model/Zone";
|
import { ZoneEventListener } from "_Model/Zone";
|
||||||
|
|
||||||
export enum GameRoomPolicyTypes {
|
export enum GameRoomPolicyTypes {
|
||||||
ANONYMUS_POLICY = 1,
|
ANONYMUS_POLICY = 1,
|
||||||
@ -17,13 +17,11 @@ export class PusherRoom {
|
|||||||
public tags: string[];
|
public tags: string[];
|
||||||
public policyType: GameRoomPolicyTypes;
|
public policyType: GameRoomPolicyTypes;
|
||||||
public readonly roomSlug: string;
|
public readonly roomSlug: string;
|
||||||
public readonly worldSlug: string = '';
|
public readonly worldSlug: string = "";
|
||||||
public readonly organizationSlug: string = '';
|
public readonly organizationSlug: string = "";
|
||||||
private versionNumber: number = 1;
|
private versionNumber: number = 1;
|
||||||
|
|
||||||
constructor(public readonly roomId: string,
|
constructor(public readonly roomId: string, private socketListener: ZoneEventListener) {
|
||||||
private socketListener: ZoneEventListener)
|
|
||||||
{
|
|
||||||
this.public = isRoomAnonymous(roomId);
|
this.public = isRoomAnonymous(roomId);
|
||||||
this.tags = [];
|
this.tags = [];
|
||||||
this.policyType = GameRoomPolicyTypes.ANONYMUS_POLICY;
|
this.policyType = GameRoomPolicyTypes.ANONYMUS_POLICY;
|
||||||
@ -31,7 +29,7 @@ export class PusherRoom {
|
|||||||
if (this.public) {
|
if (this.public) {
|
||||||
this.roomSlug = extractRoomSlugPublicRoomId(this.roomId);
|
this.roomSlug = extractRoomSlugPublicRoomId(this.roomId);
|
||||||
} else {
|
} else {
|
||||||
const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId(this.roomId);
|
const { organizationSlug, worldSlug, roomSlug } = extractDataFromPrivateRoomId(this.roomId);
|
||||||
this.roomSlug = roomSlug;
|
this.roomSlug = roomSlug;
|
||||||
this.organizationSlug = organizationSlug;
|
this.organizationSlug = organizationSlug;
|
||||||
this.worldSlug = worldSlug;
|
this.worldSlug = worldSlug;
|
||||||
@ -41,11 +39,11 @@ export class PusherRoom {
|
|||||||
this.positionNotifier = new PositionDispatcher(this.roomId, 320, 320, this.socketListener);
|
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);
|
this.positionNotifier.setViewport(socket, viewport);
|
||||||
}
|
}
|
||||||
|
|
||||||
public leave(socket : ExSocketInterface){
|
public leave(socket: ExSocketInterface) {
|
||||||
this.positionNotifier.removeViewport(socket);
|
this.positionNotifier.removeViewport(socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,30 +1,30 @@
|
|||||||
//helper functions to parse room IDs
|
//helper functions to parse room IDs
|
||||||
|
|
||||||
export const isRoomAnonymous = (roomID: string): boolean => {
|
export const isRoomAnonymous = (roomID: string): boolean => {
|
||||||
if (roomID.startsWith('_/')) {
|
if (roomID.startsWith("_/")) {
|
||||||
return true;
|
return true;
|
||||||
} else if(roomID.startsWith('@/')) {
|
} else if (roomID.startsWith("@/")) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Incorrect room ID: '+roomID);
|
throw new Error("Incorrect room ID: " + roomID);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export const extractRoomSlugPublicRoomId = (roomId: string): string => {
|
export const extractRoomSlugPublicRoomId = (roomId: string): string => {
|
||||||
const idParts = roomId.split('/');
|
const idParts = roomId.split("/");
|
||||||
if (idParts.length < 3) throw new Error('Incorrect roomId: '+roomId);
|
if (idParts.length < 3) throw new Error("Incorrect roomId: " + roomId);
|
||||||
return idParts.slice(2).join('/');
|
return idParts.slice(2).join("/");
|
||||||
}
|
};
|
||||||
export interface extractDataFromPrivateRoomIdResponse {
|
export interface extractDataFromPrivateRoomIdResponse {
|
||||||
organizationSlug: string;
|
organizationSlug: string;
|
||||||
worldSlug: string;
|
worldSlug: string;
|
||||||
roomSlug: string;
|
roomSlug: string;
|
||||||
}
|
}
|
||||||
export const extractDataFromPrivateRoomId = (roomId: string): extractDataFromPrivateRoomIdResponse => {
|
export const extractDataFromPrivateRoomId = (roomId: string): extractDataFromPrivateRoomIdResponse => {
|
||||||
const idParts = roomId.split('/');
|
const idParts = roomId.split("/");
|
||||||
if (idParts.length < 4) throw new Error('Incorrect roomId: '+roomId);
|
if (idParts.length < 4) throw new Error("Incorrect roomId: " + roomId);
|
||||||
const organizationSlug = idParts[1];
|
const organizationSlug = idParts[1];
|
||||||
const worldSlug = idParts[2];
|
const worldSlug = idParts[2];
|
||||||
const roomSlug = idParts[3];
|
const roomSlug = idParts[3];
|
||||||
return {organizationSlug, worldSlug, roomSlug}
|
return { organizationSlug, worldSlug, roomSlug };
|
||||||
}
|
};
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
import {PointInterface} from "./PointInterface";
|
import { PointInterface } from "./PointInterface";
|
||||||
import {Identificable} from "./Identificable";
|
import { Identificable } from "./Identificable";
|
||||||
import {ViewportInterface} from "_Model/Websocket/ViewportMessage";
|
import { ViewportInterface } from "_Model/Websocket/ViewportMessage";
|
||||||
import {
|
import {
|
||||||
AdminPusherToBackMessage,
|
AdminPusherToBackMessage,
|
||||||
BatchMessage,
|
BatchMessage,
|
||||||
PusherToBackMessage, ServerToAdminClientMessage,
|
PusherToBackMessage,
|
||||||
|
ServerToAdminClientMessage,
|
||||||
ServerToClientMessage,
|
ServerToClientMessage,
|
||||||
SubMessage
|
SubMessage,
|
||||||
} from "../../Messages/generated/messages_pb";
|
} from "../../Messages/generated/messages_pb";
|
||||||
import {WebSocket} from "uWebSockets.js"
|
import { WebSocket } from "uWebSockets.js";
|
||||||
import {CharacterTexture} from "../../Services/AdminApi";
|
import { CharacterTexture } from "../../Services/AdminApi";
|
||||||
import {ClientDuplexStream} from "grpc";
|
import { ClientDuplexStream } from "grpc";
|
||||||
import {Zone} from "_Model/Zone";
|
import { Zone } from "_Model/Zone";
|
||||||
|
|
||||||
export type AdminConnection = ClientDuplexStream<AdminPusherToBackMessage, ServerToAdminClientMessage>;
|
export type AdminConnection = ClientDuplexStream<AdminPusherToBackMessage, ServerToAdminClientMessage>;
|
||||||
|
|
||||||
export interface ExAdminSocketInterface extends WebSocket {
|
export interface ExAdminSocketInterface extends WebSocket {
|
||||||
adminConnection: AdminConnection,
|
adminConnection: AdminConnection;
|
||||||
disconnecting: boolean,
|
disconnecting: boolean;
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
import {PointInterface} from "./PointInterface";
|
import { PointInterface } from "./PointInterface";
|
||||||
import {Identificable} from "./Identificable";
|
import { Identificable } from "./Identificable";
|
||||||
import {ViewportInterface} from "_Model/Websocket/ViewportMessage";
|
import { ViewportInterface } from "_Model/Websocket/ViewportMessage";
|
||||||
import {
|
import {
|
||||||
BatchMessage,
|
BatchMessage,
|
||||||
CompanionMessage,
|
CompanionMessage,
|
||||||
PusherToBackMessage,
|
PusherToBackMessage,
|
||||||
ServerToClientMessage,
|
ServerToClientMessage,
|
||||||
SubMessage
|
SubMessage,
|
||||||
} from "../../Messages/generated/messages_pb";
|
} from "../../Messages/generated/messages_pb";
|
||||||
import {WebSocket} from "uWebSockets.js"
|
import { WebSocket } from "uWebSockets.js";
|
||||||
import {CharacterTexture} from "../../Services/AdminApi";
|
import { CharacterTexture } from "../../Services/AdminApi";
|
||||||
import {ClientDuplexStream} from "grpc";
|
import { ClientDuplexStream } from "grpc";
|
||||||
import {Zone} from "_Model/Zone";
|
import { Zone } from "_Model/Zone";
|
||||||
|
|
||||||
export type BackConnection = ClientDuplexStream<PusherToBackMessage, ServerToClientMessage>;
|
export type BackConnection = ClientDuplexStream<PusherToBackMessage, ServerToClientMessage>;
|
||||||
|
|
||||||
export interface CharacterLayer {
|
export interface CharacterLayer {
|
||||||
name: string,
|
name: string;
|
||||||
url: string|undefined
|
url: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExSocketInterface extends WebSocket, Identificable {
|
export interface ExSocketInterface extends WebSocket, Identificable {
|
||||||
@ -36,12 +36,12 @@ export interface ExSocketInterface extends WebSocket, Identificable {
|
|||||||
*/
|
*/
|
||||||
emitInBatch: (payload: SubMessage) => void;
|
emitInBatch: (payload: SubMessage) => void;
|
||||||
batchedMessages: BatchMessage;
|
batchedMessages: BatchMessage;
|
||||||
batchTimeout: NodeJS.Timeout|null;
|
batchTimeout: NodeJS.Timeout | null;
|
||||||
disconnecting: boolean,
|
disconnecting: boolean;
|
||||||
messages: unknown,
|
messages: unknown;
|
||||||
tags: string[],
|
tags: string[];
|
||||||
visitCardUrl: string|null,
|
visitCardUrl: string | null;
|
||||||
textures: CharacterTexture[],
|
textures: CharacterTexture[];
|
||||||
backConnection: BackConnection,
|
backConnection: BackConnection;
|
||||||
listenedZones: Set<Zone>;
|
listenedZones: Set<Zone>;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
export const isItemEventMessageInterface =
|
export const isItemEventMessageInterface = new tg.IsInterface()
|
||||||
new tg.IsInterface().withProperties({
|
.withProperties({
|
||||||
itemId: tg.isNumber,
|
itemId: tg.isNumber,
|
||||||
event: tg.isString,
|
event: tg.isString,
|
||||||
state: tg.isUnknown,
|
state: tg.isUnknown,
|
||||||
parameters: tg.isUnknown,
|
parameters: tg.isUnknown,
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
export type ItemEventMessageInterface = tg.GuardedType<typeof isItemEventMessageInterface>;
|
export type ItemEventMessageInterface = tg.GuardedType<typeof isItemEventMessageInterface>;
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import {PointInterface} from "./PointInterface";
|
import { PointInterface } from "./PointInterface";
|
||||||
|
|
||||||
export class Point implements PointInterface{
|
export class Point implements PointInterface {
|
||||||
constructor(public x : number, public y : number, public direction : string = "none", public moving : boolean = false) {
|
constructor(
|
||||||
}
|
public x: number,
|
||||||
|
public y: number,
|
||||||
|
public direction: string = "none",
|
||||||
|
public moving: boolean = false
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,12 @@ import * as tg from "generic-type-guard";
|
|||||||
readonly moving: boolean;
|
readonly moving: boolean;
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
export const isPointInterface =
|
export const isPointInterface = new tg.IsInterface()
|
||||||
new tg.IsInterface().withProperties({
|
.withProperties({
|
||||||
x: tg.isNumber,
|
x: tg.isNumber,
|
||||||
y: tg.isNumber,
|
y: tg.isNumber,
|
||||||
direction: tg.isString,
|
direction: tg.isString,
|
||||||
moving: tg.isBoolean
|
moving: tg.isBoolean,
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
export type PointInterface = tg.GuardedType<typeof isPointInterface>;
|
export type PointInterface = tg.GuardedType<typeof isPointInterface>;
|
||||||
|
@ -1,34 +1,33 @@
|
|||||||
import {PointInterface} from "./PointInterface";
|
import { PointInterface } from "./PointInterface";
|
||||||
import {
|
import {
|
||||||
CharacterLayerMessage,
|
CharacterLayerMessage,
|
||||||
ItemEventMessage,
|
ItemEventMessage,
|
||||||
PointMessage,
|
PointMessage,
|
||||||
PositionMessage
|
PositionMessage,
|
||||||
} from "../../Messages/generated/messages_pb";
|
} from "../../Messages/generated/messages_pb";
|
||||||
import {CharacterLayer, ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
|
import { CharacterLayer, ExSocketInterface } from "_Model/Websocket/ExSocketInterface";
|
||||||
import Direction = PositionMessage.Direction;
|
import Direction = PositionMessage.Direction;
|
||||||
import {ItemEventMessageInterface} from "_Model/Websocket/ItemEventMessage";
|
import { ItemEventMessageInterface } from "_Model/Websocket/ItemEventMessage";
|
||||||
import {PositionInterface} from "_Model/PositionInterface";
|
import { PositionInterface } from "_Model/PositionInterface";
|
||||||
|
|
||||||
export class ProtobufUtils {
|
export class ProtobufUtils {
|
||||||
|
|
||||||
public static toPositionMessage(point: PointInterface): PositionMessage {
|
public static toPositionMessage(point: PointInterface): PositionMessage {
|
||||||
let direction: Direction;
|
let direction: Direction;
|
||||||
switch (point.direction) {
|
switch (point.direction) {
|
||||||
case 'up':
|
case "up":
|
||||||
direction = Direction.UP;
|
direction = Direction.UP;
|
||||||
break;
|
break;
|
||||||
case 'down':
|
case "down":
|
||||||
direction = Direction.DOWN;
|
direction = Direction.DOWN;
|
||||||
break;
|
break;
|
||||||
case 'left':
|
case "left":
|
||||||
direction = Direction.LEFT;
|
direction = Direction.LEFT;
|
||||||
break;
|
break;
|
||||||
case 'right':
|
case "right":
|
||||||
direction = Direction.RIGHT;
|
direction = Direction.RIGHT;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error('unexpected direction');
|
throw new Error("unexpected direction");
|
||||||
}
|
}
|
||||||
|
|
||||||
const position = new PositionMessage();
|
const position = new PositionMessage();
|
||||||
@ -44,16 +43,16 @@ export class ProtobufUtils {
|
|||||||
let direction: string;
|
let direction: string;
|
||||||
switch (position.getDirection()) {
|
switch (position.getDirection()) {
|
||||||
case Direction.UP:
|
case Direction.UP:
|
||||||
direction = 'up';
|
direction = "up";
|
||||||
break;
|
break;
|
||||||
case Direction.DOWN:
|
case Direction.DOWN:
|
||||||
direction = 'down';
|
direction = "down";
|
||||||
break;
|
break;
|
||||||
case Direction.LEFT:
|
case Direction.LEFT:
|
||||||
direction = 'left';
|
direction = "left";
|
||||||
break;
|
break;
|
||||||
case Direction.RIGHT:
|
case Direction.RIGHT:
|
||||||
direction = 'right';
|
direction = "right";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error("Unexpected direction");
|
throw new Error("Unexpected direction");
|
||||||
@ -82,7 +81,7 @@ export class ProtobufUtils {
|
|||||||
event: itemEventMessage.getEvent(),
|
event: itemEventMessage.getEvent(),
|
||||||
parameters: JSON.parse(itemEventMessage.getParametersjson()),
|
parameters: JSON.parse(itemEventMessage.getParametersjson()),
|
||||||
state: JSON.parse(itemEventMessage.getStatejson()),
|
state: JSON.parse(itemEventMessage.getStatejson()),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static toItemEventProtobuf(itemEvent: ItemEventMessageInterface): ItemEventMessage {
|
public static toItemEventProtobuf(itemEvent: ItemEventMessageInterface): ItemEventMessage {
|
||||||
@ -96,7 +95,7 @@ export class ProtobufUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static toCharacterLayerMessages(characterLayers: CharacterLayer[]): CharacterLayerMessage[] {
|
public static toCharacterLayerMessages(characterLayers: CharacterLayer[]): CharacterLayerMessage[] {
|
||||||
return characterLayers.map(function(characterLayer): CharacterLayerMessage {
|
return characterLayers.map(function (characterLayer): CharacterLayerMessage {
|
||||||
const message = new CharacterLayerMessage();
|
const message = new CharacterLayerMessage();
|
||||||
message.setName(characterLayer.name);
|
message.setName(characterLayer.name);
|
||||||
if (characterLayer.url) {
|
if (characterLayer.url) {
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
export const isViewport =
|
export const isViewport = new tg.IsInterface()
|
||||||
new tg.IsInterface().withProperties({
|
.withProperties({
|
||||||
left: tg.isNumber,
|
left: tg.isNumber,
|
||||||
top: tg.isNumber,
|
top: tg.isNumber,
|
||||||
right: tg.isNumber,
|
right: tg.isNumber,
|
||||||
bottom: tg.isNumber,
|
bottom: tg.isNumber,
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
export type ViewportInterface = tg.GuardedType<typeof isViewport>;
|
export type ViewportInterface = tg.GuardedType<typeof isViewport>;
|
||||||
|
@ -1,16 +1,23 @@
|
|||||||
import {ExSocketInterface} from "./Websocket/ExSocketInterface";
|
import { ExSocketInterface } from "./Websocket/ExSocketInterface";
|
||||||
import {apiClientRepository} from "../Services/ApiClientRepository";
|
import { apiClientRepository } from "../Services/ApiClientRepository";
|
||||||
import {
|
import {
|
||||||
BatchToPusherMessage,
|
BatchToPusherMessage,
|
||||||
CharacterLayerMessage, GroupLeftZoneMessage, GroupUpdateMessage, GroupUpdateZoneMessage,
|
CharacterLayerMessage,
|
||||||
PointMessage, PositionMessage, UserJoinedMessage,
|
GroupLeftZoneMessage,
|
||||||
UserJoinedZoneMessage, UserLeftZoneMessage, UserMovedMessage,
|
GroupUpdateMessage,
|
||||||
|
GroupUpdateZoneMessage,
|
||||||
|
PointMessage,
|
||||||
|
PositionMessage,
|
||||||
|
UserJoinedMessage,
|
||||||
|
UserJoinedZoneMessage,
|
||||||
|
UserLeftZoneMessage,
|
||||||
|
UserMovedMessage,
|
||||||
ZoneMessage,
|
ZoneMessage,
|
||||||
EmoteEventMessage,
|
EmoteEventMessage,
|
||||||
CompanionMessage
|
CompanionMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import {ClientReadableStream} from "grpc";
|
import { ClientReadableStream } from "grpc";
|
||||||
import {PositionDispatcher} from "_Model/PositionDispatcher";
|
import { PositionDispatcher } from "_Model/PositionDispatcher";
|
||||||
import Debug from "debug";
|
import Debug from "debug";
|
||||||
|
|
||||||
const debug = Debug("zone");
|
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 type LeavesCallback = (thing: Movable, listener: User) => void;*/
|
||||||
|
|
||||||
export class UserDescriptor {
|
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)) {
|
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 {
|
public static createFromUserJoinedZoneMessage(message: UserJoinedZoneMessage): UserDescriptor {
|
||||||
const position = message.getPosition();
|
const position = message.getPosition();
|
||||||
if (position === undefined) {
|
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) {
|
public update(userMovedMessage: UserMovedMessage) {
|
||||||
const position = userMovedMessage.getPosition();
|
const position = userMovedMessage.getPosition();
|
||||||
if (position === undefined) {
|
if (position === undefined) {
|
||||||
throw new Error('Missing position');
|
throw new Error("Missing position");
|
||||||
}
|
}
|
||||||
this.position = position;
|
this.position = position;
|
||||||
}
|
}
|
||||||
@ -78,13 +99,12 @@ export class UserDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class GroupDescriptor {
|
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 {
|
public static createFromGroupUpdateZoneMessage(message: GroupUpdateZoneMessage): GroupDescriptor {
|
||||||
const position = message.getPosition();
|
const position = message.getPosition();
|
||||||
if (position === undefined) {
|
if (position === undefined) {
|
||||||
throw new Error('Missing position');
|
throw new Error("Missing position");
|
||||||
}
|
}
|
||||||
return new GroupDescriptor(message.getGroupid(), message.getGroupsize(), position);
|
return new GroupDescriptor(message.getGroupid(), message.getGroupsize(), position);
|
||||||
}
|
}
|
||||||
@ -97,7 +117,7 @@ export class GroupDescriptor {
|
|||||||
public toGroupUpdateMessage(): GroupUpdateMessage {
|
public toGroupUpdateMessage(): GroupUpdateMessage {
|
||||||
const groupUpdateMessage = new GroupUpdateMessage();
|
const groupUpdateMessage = new GroupUpdateMessage();
|
||||||
if (!Number.isInteger(this.groupId)) {
|
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.setGroupid(this.groupId);
|
||||||
groupUpdateMessage.setGroupsize(this.groupSize);
|
groupUpdateMessage.setGroupsize(this.groupSize);
|
||||||
@ -108,8 +128,8 @@ export class GroupDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ZoneDescriptor {
|
interface ZoneDescriptor {
|
||||||
x: number,
|
x: number;
|
||||||
y: number
|
y: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Zone {
|
export class Zone {
|
||||||
@ -120,21 +140,26 @@ export class Zone {
|
|||||||
private backConnection!: ClientReadableStream<BatchToPusherMessage>;
|
private backConnection!: ClientReadableStream<BatchToPusherMessage>;
|
||||||
private isClosing: boolean = false;
|
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.
|
* Creates a connection to the back server to track the users.
|
||||||
*/
|
*/
|
||||||
public async init(): Promise<void> {
|
public async init(): Promise<void> {
|
||||||
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 apiClient = await apiClientRepository.getClient(this.positionDispatcher.roomId);
|
||||||
const zoneMessage = new ZoneMessage();
|
const zoneMessage = new ZoneMessage();
|
||||||
zoneMessage.setRoomid(this.positionDispatcher.roomId);
|
zoneMessage.setRoomid(this.positionDispatcher.roomId);
|
||||||
zoneMessage.setX(this.x);
|
zoneMessage.setX(this.x);
|
||||||
zoneMessage.setY(this.y);
|
zoneMessage.setY(this.y);
|
||||||
this.backConnection = apiClient.listenZone(zoneMessage);
|
this.backConnection = apiClient.listenZone(zoneMessage);
|
||||||
this.backConnection.on('data', (batch: BatchToPusherMessage) => {
|
this.backConnection.on("data", (batch: BatchToPusherMessage) => {
|
||||||
for (const message of batch.getPayloadList()) {
|
for (const message of batch.getPayloadList()) {
|
||||||
if (message.hasUserjoinedzonemessage()) {
|
if (message.hasUserjoinedzonemessage()) {
|
||||||
const userJoinedZoneMessage = message.getUserjoinedzonemessage() as UserJoinedZoneMessage;
|
const userJoinedZoneMessage = message.getUserjoinedzonemessage() as UserJoinedZoneMessage;
|
||||||
@ -179,33 +204,32 @@ export class Zone {
|
|||||||
const userDescriptor = this.users.get(userId);
|
const userDescriptor = this.users.get(userId);
|
||||||
|
|
||||||
if (userDescriptor === undefined) {
|
if (userDescriptor === undefined) {
|
||||||
console.error('Unexpected move message received for user "'+userId+'"');
|
console.error('Unexpected move message received for user "' + userId + '"');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
userDescriptor.update(userMovedMessage);
|
userDescriptor.update(userMovedMessage);
|
||||||
|
|
||||||
this.notifyUserMove(userDescriptor);
|
this.notifyUserMove(userDescriptor);
|
||||||
} else if(message.hasEmoteeventmessage()) {
|
} else if (message.hasEmoteeventmessage()) {
|
||||||
const emoteEventMessage = message.getEmoteeventmessage() as EmoteEventMessage;
|
const emoteEventMessage = message.getEmoteeventmessage() as EmoteEventMessage;
|
||||||
this.notifyEmote(emoteEventMessage);
|
this.notifyEmote(emoteEventMessage);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unexpected message');
|
throw new Error("Unexpected message");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.backConnection.on('error', (e) => {
|
this.backConnection.on("error", (e) => {
|
||||||
if (!this.isClosing) {
|
if (!this.isClosing) {
|
||||||
debug('Error on back connection')
|
debug("Error on back connection");
|
||||||
this.close();
|
this.close();
|
||||||
this.onBackFailure(e, this);
|
this.onBackFailure(e, this);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.backConnection.on('close', () => {
|
this.backConnection.on("close", () => {
|
||||||
if (!this.isClosing) {
|
if (!this.isClosing) {
|
||||||
debug('Close on back connection')
|
debug("Close on back connection");
|
||||||
this.close();
|
this.close();
|
||||||
this.onBackFailure(null, this);
|
this.onBackFailure(null, this);
|
||||||
}
|
}
|
||||||
@ -213,7 +237,7 @@ export class Zone {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public close(): void {
|
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.isClosing = true;
|
||||||
this.backConnection.cancel();
|
this.backConnection.cancel();
|
||||||
}
|
}
|
||||||
@ -225,7 +249,7 @@ export class Zone {
|
|||||||
/**
|
/**
|
||||||
* Notify listeners of this zone that this user entered
|
* 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) {
|
for (const listener of this.listeners) {
|
||||||
if (listener.userId === user.userId) {
|
if (listener.userId === user.userId) {
|
||||||
continue;
|
continue;
|
||||||
@ -241,7 +265,7 @@ export class Zone {
|
|||||||
/**
|
/**
|
||||||
* Notify listeners of this zone that this group entered
|
* 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) {
|
for (const listener of this.listeners) {
|
||||||
if (oldZone === undefined || !this.isListeningZone(listener, oldZone.x, oldZone.y)) {
|
if (oldZone === undefined || !this.isListeningZone(listener, oldZone.x, oldZone.y)) {
|
||||||
this.socketListener.onGroupEnters(group, listener);
|
this.socketListener.onGroupEnters(group, listener);
|
||||||
@ -254,7 +278,7 @@ export class Zone {
|
|||||||
/**
|
/**
|
||||||
* Notify listeners of this zone that this user left
|
* 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) {
|
for (const listener of this.listeners) {
|
||||||
if (listener.userId === userId) {
|
if (listener.userId === userId) {
|
||||||
continue;
|
continue;
|
||||||
@ -279,7 +303,7 @@ export class Zone {
|
|||||||
/**
|
/**
|
||||||
* Notify listeners of this zone that this group left
|
* 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) {
|
for (const listener of this.listeners) {
|
||||||
if (listener.groupId === groupId) {
|
if (listener.groupId === groupId) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { App as _App, AppOptions } from 'uWebSockets.js';
|
import { App as _App, AppOptions } from "uWebSockets.js";
|
||||||
import BaseApp from './baseapp';
|
import BaseApp from "./baseapp";
|
||||||
import { extend } from './utils';
|
import { extend } from "./utils";
|
||||||
import { UwsApp } from './types';
|
import { UwsApp } from "./types";
|
||||||
|
|
||||||
class App extends (<UwsApp>_App) {
|
class App extends (<UwsApp>_App) {
|
||||||
constructor(options: AppOptions = {}) {
|
constructor(options: AppOptions = {}) {
|
||||||
super(options); // eslint-disable-line constructor-super
|
super(options); // eslint-disable-line constructor-super
|
||||||
extend(this, new BaseApp());
|
extend(this, new BaseApp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -1,116 +1,109 @@
|
|||||||
import { Readable } from 'stream';
|
import { Readable } from "stream";
|
||||||
import { us_listen_socket_close, TemplatedApp, HttpResponse, HttpRequest } from 'uWebSockets.js';
|
import { us_listen_socket_close, TemplatedApp, HttpResponse, HttpRequest } from "uWebSockets.js";
|
||||||
|
|
||||||
import formData from './formdata';
|
import formData from "./formdata";
|
||||||
import { stob } from './utils';
|
import { stob } from "./utils";
|
||||||
import { Handler } from './types';
|
import { Handler } from "./types";
|
||||||
import {join} from "path";
|
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 noOp = () => true;
|
||||||
|
|
||||||
const handleBody = (res: HttpResponse, req: HttpRequest) => {
|
const handleBody = (res: HttpResponse, req: HttpRequest) => {
|
||||||
const contType = req.getHeader('content-type');
|
const contType = req.getHeader("content-type");
|
||||||
|
|
||||||
res.bodyStream = function() {
|
res.bodyStream = function () {
|
||||||
const stream = new Readable();
|
const stream = new Readable();
|
||||||
stream._read = noOp; // eslint-disable-line @typescript-eslint/unbound-method
|
stream._read = noOp; // eslint-disable-line @typescript-eslint/unbound-method
|
||||||
|
|
||||||
this.onData((ab: ArrayBuffer, isLast: boolean) => {
|
this.onData((ab: ArrayBuffer, isLast: boolean) => {
|
||||||
// uint and then slicing is bit faster than slice and then uint
|
// 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
|
stream.push(new Uint8Array(ab.slice((ab as any).byteOffset, ab.byteLength))); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
if (isLast) {
|
if (isLast) {
|
||||||
stream.push(null);
|
stream.push(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return stream;
|
return stream;
|
||||||
};
|
};
|
||||||
|
|
||||||
res.body = () => stob(res.bodyStream());
|
res.body = () => stob(res.bodyStream());
|
||||||
|
|
||||||
if (contType.includes('application/json'))
|
if (contType.includes("application/json")) res.json = async () => JSON.parse(await res.body());
|
||||||
res.json = async () => JSON.parse(await res.body());
|
if (contTypes.map((t) => contType.includes(t)).includes(true)) res.formData = formData.bind(res, contType);
|
||||||
if (contTypes.map(t => contType.includes(t)).includes(true))
|
|
||||||
res.formData = formData.bind(res, contType);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class BaseApp {
|
class BaseApp {
|
||||||
_sockets = new Map();
|
_sockets = new Map();
|
||||||
ws!: TemplatedApp['ws'];
|
ws!: TemplatedApp["ws"];
|
||||||
get!: TemplatedApp['get'];
|
get!: TemplatedApp["get"];
|
||||||
_post!: TemplatedApp['post'];
|
_post!: TemplatedApp["post"];
|
||||||
_put!: TemplatedApp['put'];
|
_put!: TemplatedApp["put"];
|
||||||
_patch!: TemplatedApp['patch'];
|
_patch!: TemplatedApp["patch"];
|
||||||
_listen!: TemplatedApp['listen'];
|
_listen!: TemplatedApp["listen"];
|
||||||
|
|
||||||
post(pattern: string, handler: Handler) {
|
post(pattern: string, handler: Handler) {
|
||||||
if (typeof handler !== 'function')
|
if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`);
|
||||||
throw Error(`handler should be a function, given ${typeof handler}.`);
|
this._post(pattern, (res, req) => {
|
||||||
this._post(pattern, (res, req) => {
|
handleBody(res, req);
|
||||||
handleBody(res, req);
|
handler(res, req);
|
||||||
handler(res, req);
|
});
|
||||||
});
|
return this;
|
||||||
return this;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
put(pattern: string, handler: Handler) {
|
put(pattern: string, handler: Handler) {
|
||||||
if (typeof handler !== 'function')
|
if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`);
|
||||||
throw Error(`handler should be a function, given ${typeof handler}.`);
|
this._put(pattern, (res, req) => {
|
||||||
this._put(pattern, (res, req) => {
|
handleBody(res, req);
|
||||||
handleBody(res, req);
|
|
||||||
|
|
||||||
handler(res, req);
|
handler(res, req);
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
patch(pattern: string, handler: Handler) {
|
patch(pattern: string, handler: Handler) {
|
||||||
if (typeof handler !== 'function')
|
if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`);
|
||||||
throw Error(`handler should be a function, given ${typeof handler}.`);
|
this._patch(pattern, (res, req) => {
|
||||||
this._patch(pattern, (res, req) => {
|
handleBody(res, req);
|
||||||
handleBody(res, req);
|
|
||||||
|
|
||||||
handler(res, req);
|
handler(res, req);
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
listen(h: string | number, p: Function | number = noOp, cb?: Function) {
|
listen(h: string | number, p: Function | number = noOp, cb?: Function) {
|
||||||
if (typeof p === 'number' && typeof h === 'string') {
|
if (typeof p === "number" && typeof h === "string") {
|
||||||
this._listen(h, p, socket => {
|
this._listen(h, p, (socket) => {
|
||||||
this._sockets.set(p, socket);
|
this._sockets.set(p, socket);
|
||||||
if (cb === undefined) {
|
if (cb === undefined) {
|
||||||
throw new Error('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);
|
|
||||||
});
|
return this;
|
||||||
} 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;
|
close(port: null | number = null) {
|
||||||
}
|
if (port) {
|
||||||
|
this._sockets.has(port) && us_listen_socket_close(this._sockets.get(port));
|
||||||
close(port: null | number = null) {
|
this._sockets.delete(port);
|
||||||
if (port) {
|
} else {
|
||||||
this._sockets.has(port) && us_listen_socket_close(this._sockets.get(port));
|
this._sockets.forEach((app) => {
|
||||||
this._sockets.delete(port);
|
us_listen_socket_close(app);
|
||||||
} else {
|
});
|
||||||
this._sockets.forEach(app => {
|
this._sockets.clear();
|
||||||
us_listen_socket_close(app);
|
}
|
||||||
});
|
return this;
|
||||||
this._sockets.clear();
|
|
||||||
}
|
}
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BaseApp;
|
export default BaseApp;
|
||||||
|
@ -1,100 +1,99 @@
|
|||||||
import { createWriteStream } from 'fs';
|
import { createWriteStream } from "fs";
|
||||||
import { join, dirname } from 'path';
|
import { join, dirname } from "path";
|
||||||
import Busboy from 'busboy';
|
import Busboy from "busboy";
|
||||||
import mkdirp from 'mkdirp';
|
import mkdirp from "mkdirp";
|
||||||
|
|
||||||
function formData(
|
function formData(
|
||||||
contType: string,
|
contType: string,
|
||||||
options: busboy.BusboyConfig & {
|
options: busboy.BusboyConfig & {
|
||||||
abortOnLimit?: boolean;
|
abortOnLimit?: boolean;
|
||||||
tmpDir?: string;
|
tmpDir?: string;
|
||||||
onFile?: (
|
onFile?: (
|
||||||
fieldname: string,
|
fieldname: string,
|
||||||
file: NodeJS.ReadableStream,
|
file: NodeJS.ReadableStream,
|
||||||
filename: string,
|
filename: string,
|
||||||
encoding: string,
|
encoding: string,
|
||||||
mimetype: string
|
mimetype: string
|
||||||
) => string;
|
) => string;
|
||||||
onField?: (fieldname: string, value: any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any
|
onField?: (fieldname: string, value: any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
filename?: (oldName: string) => string;
|
filename?: (oldName: string) => string;
|
||||||
} = {}
|
} = {}
|
||||||
) {
|
) {
|
||||||
console.log('Enter form data');
|
console.log("Enter form data");
|
||||||
options.headers = {
|
options.headers = {
|
||||||
'content-type': contType
|
"content-type": contType,
|
||||||
};
|
};
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const busb = new Busboy(options);
|
const busb = new Busboy(options);
|
||||||
const ret = {};
|
const ret = {};
|
||||||
|
|
||||||
this.bodyStream().pipe(busb);
|
this.bodyStream().pipe(busb);
|
||||||
|
|
||||||
busb.on('limit', () => {
|
busb.on("limit", () => {
|
||||||
if (options.abortOnLimit) {
|
if (options.abortOnLimit) {
|
||||||
reject(Error('limit'));
|
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(
|
function setRetValue(
|
||||||
ret: { [x: string]: any }, // eslint-disable-line @typescript-eslint/no-explicit-any
|
ret: { [x: string]: any }, // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
fieldname: string,
|
fieldname: string,
|
||||||
value: { filename: string; encoding: string; mimetype: string; filePath?: string } | any // eslint-disable-line @typescript-eslint/no-explicit-any
|
value: { filename: string; encoding: string; mimetype: string; filePath?: string } | any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
) {
|
) {
|
||||||
if (fieldname.endsWith('[]')) {
|
if (fieldname.endsWith("[]")) {
|
||||||
fieldname = fieldname.slice(0, fieldname.length - 2);
|
fieldname = fieldname.slice(0, fieldname.length - 2);
|
||||||
if (Array.isArray(ret[fieldname])) {
|
if (Array.isArray(ret[fieldname])) {
|
||||||
ret[fieldname].push(value);
|
ret[fieldname].push(value);
|
||||||
|
} else {
|
||||||
|
ret[fieldname] = [value];
|
||||||
|
}
|
||||||
} else {
|
} 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;
|
export default formData;
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { SSLApp as _SSLApp, AppOptions } from 'uWebSockets.js';
|
import { SSLApp as _SSLApp, AppOptions } from "uWebSockets.js";
|
||||||
import BaseApp from './baseapp';
|
import BaseApp from "./baseapp";
|
||||||
import { extend } from './utils';
|
import { extend } from "./utils";
|
||||||
import { UwsApp } from './types';
|
import { UwsApp } from "./types";
|
||||||
|
|
||||||
class SSLApp extends (<UwsApp>_SSLApp) {
|
class SSLApp extends (<UwsApp>_SSLApp) {
|
||||||
constructor(options: AppOptions) {
|
constructor(options: AppOptions) {
|
||||||
super(options); // eslint-disable-line constructor-super
|
super(options); // eslint-disable-line constructor-super
|
||||||
extend(this, new BaseApp());
|
extend(this, new BaseApp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SSLApp;
|
export default SSLApp;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { AppOptions, TemplatedApp, HttpResponse, HttpRequest } from 'uWebSockets.js';
|
import { AppOptions, TemplatedApp, HttpResponse, HttpRequest } from "uWebSockets.js";
|
||||||
|
|
||||||
export type UwsApp = {
|
export type UwsApp = {
|
||||||
(options: AppOptions): TemplatedApp;
|
(options: AppOptions): TemplatedApp;
|
||||||
new (options: AppOptions): TemplatedApp;
|
new (options: AppOptions): TemplatedApp;
|
||||||
prototype: TemplatedApp;
|
prototype: TemplatedApp;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Handler = (res: HttpResponse, req: HttpRequest) => void;
|
export type Handler = (res: HttpResponse, req: HttpRequest) => void;
|
||||||
|
@ -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
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const ownProps = Object.getOwnPropertyNames(Object.getPrototypeOf(from)).concat(
|
function extend(who: any, from: any, overwrite = true) {
|
||||||
Object.keys(from)
|
const ownProps = Object.getOwnPropertyNames(Object.getPrototypeOf(from)).concat(Object.keys(from));
|
||||||
);
|
ownProps.forEach((prop) => {
|
||||||
ownProps.forEach(prop => {
|
if (prop === "constructor" || from[prop] === undefined) return;
|
||||||
if (prop === 'constructor' || from[prop] === undefined) return;
|
if (who[prop] && overwrite) {
|
||||||
if (who[prop] && overwrite) {
|
who[`_${prop}`] = who[prop];
|
||||||
who[`_${prop}`] = who[prop];
|
}
|
||||||
}
|
if (typeof from[prop] === "function") who[prop] = from[prop].bind(who);
|
||||||
if (typeof from[prop] === 'function') who[prop] = from[prop].bind(who);
|
else who[prop] = from[prop];
|
||||||
else who[prop] = from[prop];
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function stob(stream: ReadStream): Promise<Buffer> {
|
function stob(stream: ReadStream): Promise<Buffer> {
|
||||||
return new Promise(resolve => {
|
return new Promise((resolve) => {
|
||||||
const buffers: Buffer[] = [];
|
const buffers: Buffer[] = [];
|
||||||
stream.on('data', buffers.push.bind(buffers));
|
stream.on("data", buffers.push.bind(buffers));
|
||||||
|
|
||||||
stream.on('end', () => {
|
stream.on("end", () => {
|
||||||
switch (buffers.length) {
|
switch (buffers.length) {
|
||||||
case 0:
|
case 0:
|
||||||
resolve(Buffer.allocUnsafe(0));
|
resolve(Buffer.allocUnsafe(0));
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
resolve(buffers[0]);
|
resolve(buffers[0]);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
resolve(Buffer.concat(buffers));
|
resolve(Buffer.concat(buffers));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { extend, stob };
|
export { extend, stob };
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import { parse } from 'query-string';
|
import { parse } from "query-string";
|
||||||
import { HttpRequest } from 'uWebSockets.js';
|
import { HttpRequest } from "uWebSockets.js";
|
||||||
import App from './server/app';
|
import App from "./server/app";
|
||||||
import SSLApp from './server/sslapp';
|
import SSLApp from "./server/sslapp";
|
||||||
import * as types from './server/types';
|
import * as types from "./server/types";
|
||||||
|
|
||||||
const getQuery = (req: HttpRequest) => {
|
const getQuery = (req: HttpRequest) => {
|
||||||
return parse(req.getQuery());
|
return parse(req.getQuery());
|
||||||
};
|
};
|
||||||
|
|
||||||
export { App, SSLApp, getQuery };
|
export { App, SSLApp, getQuery };
|
||||||
export * from './server/types';
|
export * from "./server/types";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
App,
|
App,
|
||||||
SSLApp,
|
SSLApp,
|
||||||
getQuery,
|
getQuery,
|
||||||
...types
|
...types,
|
||||||
};
|
};
|
||||||
|
@ -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 Axios from "axios";
|
||||||
import {GameRoomPolicyTypes} from "_Model/PusherRoom";
|
import { GameRoomPolicyTypes } from "_Model/PusherRoom";
|
||||||
|
|
||||||
export interface AdminApiData {
|
export interface AdminApiData {
|
||||||
organizationSlug: string
|
organizationSlug: string;
|
||||||
worldSlug: string
|
worldSlug: string;
|
||||||
roomSlug: string
|
roomSlug: string;
|
||||||
mapUrlStart: string
|
mapUrlStart: string;
|
||||||
tags: string[]
|
tags: string[];
|
||||||
policy_type: number
|
policy_type: number;
|
||||||
userUuid: string
|
userUuid: string;
|
||||||
messages?: unknown[],
|
messages?: unknown[];
|
||||||
textures: CharacterTexture[]
|
textures: CharacterTexture[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MapDetailsData {
|
export interface MapDetailsData {
|
||||||
roomSlug: string,
|
roomSlug: string;
|
||||||
mapUrl: string,
|
mapUrl: string;
|
||||||
policy_type: GameRoomPolicyTypes,
|
policy_type: GameRoomPolicyTypes;
|
||||||
tags: string[],
|
tags: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminBannedData {
|
export interface AdminBannedData {
|
||||||
is_banned: boolean,
|
is_banned: boolean;
|
||||||
message: string
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CharacterTexture {
|
export interface CharacterTexture {
|
||||||
id: number,
|
id: number;
|
||||||
level: number,
|
level: number;
|
||||||
url: string,
|
url: string;
|
||||||
rights: string
|
rights: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FetchMemberDataByUuidResponse {
|
export interface FetchMemberDataByUuidResponse {
|
||||||
uuid: string;
|
uuid: string;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
visitCardUrl: string|null;
|
visitCardUrl: string | null;
|
||||||
textures: CharacterTexture[];
|
textures: CharacterTexture[];
|
||||||
messages: unknown[];
|
messages: unknown[];
|
||||||
anonymous?: boolean;
|
anonymous?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class AdminApi {
|
class AdminApi {
|
||||||
|
async fetchMapDetails(
|
||||||
async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise<MapDetailsData> {
|
organizationSlug: string,
|
||||||
|
worldSlug: string,
|
||||||
|
roomSlug: string | undefined
|
||||||
|
): Promise<MapDetailsData> {
|
||||||
if (!ADMIN_API_URL) {
|
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,
|
organizationSlug,
|
||||||
worldSlug
|
worldSlug,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (roomSlug) {
|
if (roomSlug) {
|
||||||
params.roomSlug = roomSlug;
|
params.roomSlug = roomSlug;
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await Axios.get(ADMIN_API_URL + '/api/map',
|
const res = await Axios.get(ADMIN_API_URL + "/api/map", {
|
||||||
{
|
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
||||||
headers: {"Authorization": `${ADMIN_API_TOKEN}`},
|
params,
|
||||||
params
|
});
|
||||||
}
|
|
||||||
)
|
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchMemberDataByUuid(uuid: string, roomId: string): Promise<FetchMemberDataByUuidResponse> {
|
async fetchMemberDataByUuid(uuid: string, roomId: string): Promise<FetchMemberDataByUuidResponse> {
|
||||||
if (!ADMIN_API_URL) {
|
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',
|
const res = await Axios.get(ADMIN_API_URL + "/api/room/access", {
|
||||||
{ params: {uuid, roomId}, headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
|
params: { uuid, roomId },
|
||||||
)
|
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
||||||
|
});
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchMemberDataByToken(organizationMemberToken: string): Promise<AdminApiData> {
|
async fetchMemberDataByToken(organizationMemberToken: string): Promise<AdminApiData> {
|
||||||
if (!ADMIN_API_URL) {
|
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.
|
//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,
|
const res = await Axios.get(ADMIN_API_URL + "/api/login-url/" + organizationMemberToken, {
|
||||||
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
|
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
||||||
)
|
});
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchCheckUserByToken(organizationMemberToken: string): Promise<AdminApiData> {
|
async fetchCheckUserByToken(organizationMemberToken: string): Promise<AdminApiData> {
|
||||||
if (!ADMIN_API_URL) {
|
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.
|
//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,
|
const res = await Axios.get(ADMIN_API_URL + "/api/check-user/" + organizationMemberToken, {
|
||||||
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
|
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
||||||
)
|
});
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string, reportWorldSlug: string) {
|
reportPlayer(
|
||||||
return Axios.post(`${ADMIN_API_URL}/api/report`, {
|
reportedUserUuid: string,
|
||||||
|
reportedUserComment: string,
|
||||||
|
reporterUserUuid: string,
|
||||||
|
reportWorldSlug: string
|
||||||
|
) {
|
||||||
|
return Axios.post(
|
||||||
|
`${ADMIN_API_URL}/api/report`,
|
||||||
|
{
|
||||||
reportedUserUuid,
|
reportedUserUuid,
|
||||||
reportedUserComment,
|
reportedUserComment,
|
||||||
reporterUserUuid,
|
reporterUserUuid,
|
||||||
reportWorldSlug
|
reportWorldSlug,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {"Authorization": `${ADMIN_API_TOKEN}`}
|
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyBanUser(organizationMemberToken: string, ipAddress: string, organization: string, world: string): Promise<AdminBannedData> {
|
async verifyBanUser(
|
||||||
|
organizationMemberToken: string,
|
||||||
|
ipAddress: string,
|
||||||
|
organization: string,
|
||||||
|
world: string
|
||||||
|
): Promise<AdminBannedData> {
|
||||||
if (!ADMIN_API_URL) {
|
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.
|
//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,
|
return Axios.get(
|
||||||
{headers: {"Authorization": `${ADMIN_API_TOKEN}`}}
|
ADMIN_API_URL +
|
||||||
|
"/api/check-moderate-user/" +
|
||||||
|
organization +
|
||||||
|
"/" +
|
||||||
|
world +
|
||||||
|
"?ipAddress=" +
|
||||||
|
ipAddress +
|
||||||
|
"&token=" +
|
||||||
|
organizationMemberToken,
|
||||||
|
{ headers: { Authorization: `${ADMIN_API_TOKEN}` } }
|
||||||
).then((data) => {
|
).then((data) => {
|
||||||
return data.data;
|
return data.data;
|
||||||
});
|
});
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* A class to get connections to the correct "api" server given a room name.
|
* A class to get connections to the correct "api" server given a room name.
|
||||||
*/
|
*/
|
||||||
import {RoomManagerClient} from "../Messages/generated/messages_grpc_pb";
|
import { RoomManagerClient } from "../Messages/generated/messages_grpc_pb";
|
||||||
import grpc from 'grpc';
|
import grpc from "grpc";
|
||||||
import crypto from 'crypto';
|
import crypto from "crypto";
|
||||||
import {API_URL} from "../Enum/EnvironmentVariable";
|
import { API_URL } from "../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
import Debug from "debug";
|
import Debug from "debug";
|
||||||
|
|
||||||
const debug = Debug('apiClientRespository');
|
const debug = Debug("apiClientRespository");
|
||||||
|
|
||||||
class ApiClientRepository {
|
class ApiClientRepository {
|
||||||
private roomManagerClients: RoomManagerClient[] = [];
|
private roomManagerClients: RoomManagerClient[] = [];
|
||||||
@ -16,23 +16,26 @@ class ApiClientRepository {
|
|||||||
public constructor(private apiUrls: string[]) {}
|
public constructor(private apiUrls: string[]) {}
|
||||||
|
|
||||||
public async getClient(roomId: string): Promise<RoomManagerClient> {
|
public async getClient(roomId: string): Promise<RoomManagerClient> {
|
||||||
const array = new Uint32Array(crypto.createHash('md5').update(roomId).digest());
|
const array = new Uint32Array(crypto.createHash("md5").update(roomId).digest());
|
||||||
const index = array[0] % this.apiUrls.length;
|
const index = array[0] % this.apiUrls.length;
|
||||||
|
|
||||||
let client = this.roomManagerClients[index];
|
let client = this.roomManagerClients[index];
|
||||||
if (client === undefined) {
|
if (client === undefined) {
|
||||||
this.roomManagerClients[index] = client = new RoomManagerClient(this.apiUrls[index], grpc.credentials.createInsecure());
|
this.roomManagerClients[index] = client = new RoomManagerClient(
|
||||||
debug('Mapping room %s to API server %s', roomId, this.apiUrls[index])
|
this.apiUrls[index],
|
||||||
|
grpc.credentials.createInsecure()
|
||||||
|
);
|
||||||
|
debug("Mapping room %s to API server %s", roomId, this.apiUrls[index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(client);
|
return Promise.resolve(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAllClients(): Promise<RoomManagerClient[]> {
|
public async getAllClients(): Promise<RoomManagerClient[]> {
|
||||||
return [await this.getClient('')];
|
return [await this.getClient("")];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiClientRepository = new ApiClientRepository(API_URL.split(','));
|
const apiClientRepository = new ApiClientRepository(API_URL.split(","));
|
||||||
|
|
||||||
export { apiClientRepository };
|
export { apiClientRepository };
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export const arrayIntersect = (array1: string[], array2: string[]) : boolean => {
|
export const arrayIntersect = (array1: string[], array2: string[]): boolean => {
|
||||||
return array1.filter(value => array2.includes(value)).length > 0;
|
return array1.filter((value) => array2.includes(value)).length > 0;
|
||||||
}
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const EventEmitter = require('events');
|
const EventEmitter = require("events");
|
||||||
|
|
||||||
const clientJoinEvent = 'clientJoin';
|
const clientJoinEvent = "clientJoin";
|
||||||
const clientLeaveEvent = 'clientLeave';
|
const clientLeaveEvent = "clientLeave";
|
||||||
|
|
||||||
class ClientEventsEmitter extends EventEmitter {
|
class ClientEventsEmitter extends EventEmitter {
|
||||||
emitClientJoin(clientUUid: string, roomId: string): void {
|
emitClientJoin(clientUUid: string, roomId: string): void {
|
||||||
@ -11,7 +11,7 @@ class ClientEventsEmitter extends EventEmitter {
|
|||||||
emitClientLeave(clientUUid: string, roomId: string): void {
|
emitClientLeave(clientUUid: string, roomId: string): void {
|
||||||
this.emit(clientLeaveEvent, clientUUid, roomId);
|
this.emit(clientLeaveEvent, clientUUid, roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
registerToClientJoin(callback: (clientUUid: string, roomId: string) => void): void {
|
registerToClientJoin(callback: (clientUUid: string, roomId: string) => void): void {
|
||||||
this.on(clientJoinEvent, callback);
|
this.on(clientJoinEvent, callback);
|
||||||
}
|
}
|
||||||
@ -29,4 +29,4 @@ class ClientEventsEmitter extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const clientEventsEmitter = new ClientEventsEmitter();
|
export const clientEventsEmitter = new ClientEventsEmitter();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {CPU_OVERHEAT_THRESHOLD} from "../Enum/EnvironmentVariable";
|
import { CPU_OVERHEAT_THRESHOLD } from "../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
function secNSec2ms(secNSec: Array<number>|number) {
|
function secNSec2ms(secNSec: Array<number> | number) {
|
||||||
if (Array.isArray(secNSec)) {
|
if (Array.isArray(secNSec)) {
|
||||||
return secNSec[0] * 1000 + secNSec[1] / 1000000;
|
return secNSec[0] * 1000 + secNSec[1] / 1000000;
|
||||||
}
|
}
|
||||||
@ -12,17 +12,17 @@ class CpuTracker {
|
|||||||
private overHeating: boolean = false;
|
private overHeating: boolean = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
let time = process.hrtime.bigint()
|
let time = process.hrtime.bigint();
|
||||||
let usage = process.cpuUsage()
|
let usage = process.cpuUsage();
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
const elapTime = process.hrtime.bigint();
|
const elapTime = process.hrtime.bigint();
|
||||||
const elapUsage = process.cpuUsage(usage)
|
const elapUsage = process.cpuUsage(usage);
|
||||||
usage = process.cpuUsage()
|
usage = process.cpuUsage();
|
||||||
|
|
||||||
const elapTimeMS = elapTime - time;
|
const elapTimeMS = elapTime - time;
|
||||||
const elapUserMS = secNSec2ms(elapUsage.user)
|
const elapUserMS = secNSec2ms(elapUsage.user);
|
||||||
const elapSystMS = secNSec2ms(elapUsage.system)
|
const elapSystMS = secNSec2ms(elapUsage.system);
|
||||||
this.cpuPercent = Math.round(100 * (elapUserMS + elapSystMS) / Number(elapTimeMS) * 1000000)
|
this.cpuPercent = Math.round(((100 * (elapUserMS + elapSystMS)) / Number(elapTimeMS)) * 1000000);
|
||||||
|
|
||||||
time = elapTime;
|
time = elapTime;
|
||||||
|
|
||||||
|
@ -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
|
//this class should manage all the custom metrics used by prometheus
|
||||||
class GaugeManager {
|
class GaugeManager {
|
||||||
@ -9,25 +9,25 @@ class GaugeManager {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.nbClientsGauge = new Gauge({
|
this.nbClientsGauge = new Gauge({
|
||||||
name: 'workadventure_nb_sockets',
|
name: "workadventure_nb_sockets",
|
||||||
help: 'Number of connected sockets',
|
help: "Number of connected sockets",
|
||||||
labelNames: [ ]
|
labelNames: [],
|
||||||
});
|
});
|
||||||
this.nbClientsPerRoomGauge = new Gauge({
|
this.nbClientsPerRoomGauge = new Gauge({
|
||||||
name: 'workadventure_nb_clients_per_room',
|
name: "workadventure_nb_clients_per_room",
|
||||||
help: 'Number of clients per room',
|
help: "Number of clients per room",
|
||||||
labelNames: [ 'room' ]
|
labelNames: ["room"],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.nbGroupsPerRoomCounter = new Counter({
|
this.nbGroupsPerRoomCounter = new Counter({
|
||||||
name: 'workadventure_counter_groups_per_room',
|
name: "workadventure_counter_groups_per_room",
|
||||||
help: 'Counter of groups per room',
|
help: "Counter of groups per room",
|
||||||
labelNames: [ 'room' ]
|
labelNames: ["room"],
|
||||||
});
|
});
|
||||||
this.nbGroupsPerRoomGauge = new Gauge({
|
this.nbGroupsPerRoomGauge = new Gauge({
|
||||||
name: 'workadventure_nb_groups_per_room',
|
name: "workadventure_nb_groups_per_room",
|
||||||
help: 'Number of groups per room',
|
help: "Number of groups per room",
|
||||||
labelNames: [ 'room' ]
|
labelNames: ["room"],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,13 +42,13 @@ class GaugeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
incNbGroupsPerRoomGauge(roomId: string): void {
|
incNbGroupsPerRoomGauge(roomId: string): void {
|
||||||
this.nbGroupsPerRoomCounter.inc({ room: roomId })
|
this.nbGroupsPerRoomCounter.inc({ room: roomId });
|
||||||
this.nbGroupsPerRoomGauge.inc({ room: roomId })
|
this.nbGroupsPerRoomGauge.inc({ room: roomId });
|
||||||
}
|
}
|
||||||
|
|
||||||
decNbGroupsPerRoomGauge(roomId: string): void {
|
decNbGroupsPerRoomGauge(roomId: string): void {
|
||||||
this.nbGroupsPerRoomGauge.dec({ room: roomId })
|
this.nbGroupsPerRoomGauge.dec({ room: roomId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const gaugeManager = new GaugeManager();
|
export const gaugeManager = new GaugeManager();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
|
import { ExSocketInterface } from "_Model/Websocket/ExSocketInterface";
|
||||||
import {BatchMessage, ErrorMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb";
|
import { BatchMessage, ErrorMessage, ServerToClientMessage, SubMessage } from "../Messages/generated/messages_pb";
|
||||||
import {WebSocket} from "uWebSockets.js";
|
import { WebSocket } from "uWebSockets.js";
|
||||||
|
|
||||||
export function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void {
|
export function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void {
|
||||||
socket.batchedMessages.addPayload(payload);
|
socket.batchedMessages.addPayload(payload);
|
||||||
@ -33,4 +33,3 @@ export function emitError(Client: WebSocket, message: string): void {
|
|||||||
}
|
}
|
||||||
console.warn(message);
|
console.warn(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,73 +1,77 @@
|
|||||||
import {ADMIN_API_URL, ALLOW_ARTILLERY, SECRET_KEY} from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_URL, ALLOW_ARTILLERY, SECRET_KEY } from "../Enum/EnvironmentVariable";
|
||||||
import {uuid} from "uuidv4";
|
import { uuid } from "uuidv4";
|
||||||
import Jwt from "jsonwebtoken";
|
import Jwt from "jsonwebtoken";
|
||||||
import {TokenInterface} from "../Controller/AuthenticateController";
|
import { TokenInterface } from "../Controller/AuthenticateController";
|
||||||
import {adminApi, AdminBannedData} from "../Services/AdminApi";
|
import { adminApi, AdminBannedData } from "../Services/AdminApi";
|
||||||
|
|
||||||
class JWTTokenManager {
|
class JWTTokenManager {
|
||||||
|
|
||||||
public createJWTToken(userUuid: string) {
|
public createJWTToken(userUuid: string) {
|
||||||
return Jwt.sign({userUuid: userUuid}, SECRET_KEY, {expiresIn: '200d'}); //todo: add a mechanic to refresh or recreate token
|
return Jwt.sign({ userUuid: userUuid }, SECRET_KEY, { expiresIn: "200d" }); //todo: add a mechanic to refresh or recreate token
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getUserUuidFromToken(token: unknown, ipAddress?: string, room?: string): Promise<string> {
|
public async getUserUuidFromToken(token: unknown, ipAddress?: string, room?: string): Promise<string> {
|
||||||
|
|
||||||
if (!token) {
|
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") {
|
if (typeof token !== "string") {
|
||||||
throw new Error('Token is expected to be a string');
|
throw new Error("Token is expected to be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (token === "test") {
|
||||||
if(token === 'test') {
|
|
||||||
if (ALLOW_ARTILLERY) {
|
if (ALLOW_ARTILLERY) {
|
||||||
return uuid();
|
return uuid();
|
||||||
} else {
|
} else {
|
||||||
throw new Error("In order to perform a load-testing test on this environment, you must set the ALLOW_ARTILLERY environment variable to 'true'");
|
throw new Error(
|
||||||
|
"In order to perform a load-testing test on this environment, you must set the ALLOW_ARTILLERY environment variable to 'true'"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise<string>((resolve, reject) => {
|
return new Promise<string>((resolve, reject) => {
|
||||||
Jwt.verify(token, SECRET_KEY, {},(err, tokenDecoded) => {
|
Jwt.verify(token, SECRET_KEY, {}, (err, tokenDecoded) => {
|
||||||
const tokenInterface = tokenDecoded as TokenInterface;
|
const tokenInterface = tokenDecoded as TokenInterface;
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('An authentication error happened, invalid JsonWebToken.', err);
|
console.error("An authentication error happened, invalid JsonWebToken.", err);
|
||||||
reject(new Error('An authentication error happened, invalid JsonWebToken. ' + err.message));
|
reject(new Error("An authentication error happened, invalid JsonWebToken. " + err.message));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (tokenDecoded === undefined) {
|
if (tokenDecoded === undefined) {
|
||||||
console.error('Empty token found.');
|
console.error("Empty token found.");
|
||||||
reject(new Error('Empty token found.'));
|
reject(new Error("Empty token found."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//verify token
|
//verify token
|
||||||
if (!this.isValidToken(tokenInterface)) {
|
if (!this.isValidToken(tokenInterface)) {
|
||||||
reject(new Error('Authentication error, invalid token structure.'));
|
reject(new Error("Authentication error, invalid token structure."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ADMIN_API_URL) {
|
if (ADMIN_API_URL) {
|
||||||
//verify user in admin
|
//verify user in admin
|
||||||
let promise = new Promise((resolve) => resolve());
|
let promise = new Promise((resolve) => resolve());
|
||||||
if(ipAddress && room) {
|
if (ipAddress && room) {
|
||||||
promise = this.verifyBanUser(tokenInterface.userUuid, ipAddress, room);
|
promise = this.verifyBanUser(tokenInterface.userUuid, ipAddress, room);
|
||||||
}
|
}
|
||||||
promise.then(() => {
|
promise
|
||||||
adminApi.fetchCheckUserByToken(tokenInterface.userUuid).then(() => {
|
.then(() => {
|
||||||
resolve(tokenInterface.userUuid);
|
adminApi
|
||||||
}).catch((err) => {
|
.fetchCheckUserByToken(tokenInterface.userUuid)
|
||||||
//anonymous user
|
.then(() => {
|
||||||
if (err.response && err.response.status && err.response.status === 404) {
|
resolve(tokenInterface.userUuid);
|
||||||
resolve(tokenInterface.userUuid);
|
})
|
||||||
return;
|
.catch((err) => {
|
||||||
}
|
//anonymous user
|
||||||
|
if (err.response && err.response.status && err.response.status === 404) {
|
||||||
|
resolve(tokenInterface.userUuid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
}).catch((err) => {
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
resolve(tokenInterface.userUuid);
|
resolve(tokenInterface.userUuid);
|
||||||
}
|
}
|
||||||
@ -76,30 +80,32 @@ class JWTTokenManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private verifyBanUser(userUuid: string, ipAddress: string, room: string): Promise<AdminBannedData> {
|
private verifyBanUser(userUuid: string, ipAddress: string, room: string): Promise<AdminBannedData> {
|
||||||
const parts = room.split('/');
|
const parts = room.split("/");
|
||||||
if (parts.length < 3 || parts[0] !== '@') {
|
if (parts.length < 3 || parts[0] !== "@") {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
is_banned: false,
|
is_banned: false,
|
||||||
message: ''
|
message: "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const organization = parts[1];
|
const organization = parts[1];
|
||||||
const world = parts[2];
|
const world = parts[2];
|
||||||
return adminApi.verifyBanUser(userUuid, ipAddress, organization, world).then((data: AdminBannedData) => {
|
return adminApi
|
||||||
if (data && data.is_banned) {
|
.verifyBanUser(userUuid, ipAddress, organization, world)
|
||||||
throw new Error('User was banned');
|
.then((data: AdminBannedData) => {
|
||||||
}
|
if (data && data.is_banned) {
|
||||||
return data;
|
throw new Error("User was banned");
|
||||||
}).catch((err) => {
|
}
|
||||||
throw err;
|
return data;
|
||||||
});
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private isValidToken(token: object): token is TokenInterface {
|
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();
|
export const jwtTokenManager = new JWTTokenManager();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {PusherRoom} from "../Model/PusherRoom";
|
import { PusherRoom } from "../Model/PusherRoom";
|
||||||
import {CharacterLayer, ExSocketInterface} from "../Model/Websocket/ExSocketInterface";
|
import { CharacterLayer, ExSocketInterface } from "../Model/Websocket/ExSocketInterface";
|
||||||
import {
|
import {
|
||||||
GroupDeleteMessage,
|
GroupDeleteMessage,
|
||||||
ItemEventMessage,
|
ItemEventMessage,
|
||||||
@ -31,21 +31,21 @@ import {
|
|||||||
RefreshRoomMessage,
|
RefreshRoomMessage,
|
||||||
EmotePromptMessage,
|
EmotePromptMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
||||||
import {JITSI_ISS, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
|
import { JITSI_ISS, SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable";
|
||||||
import {adminApi, CharacterTexture} from "./AdminApi";
|
import { adminApi, CharacterTexture } from "./AdminApi";
|
||||||
import {emitInBatch} from "./IoSocketHelpers";
|
import { emitInBatch } from "./IoSocketHelpers";
|
||||||
import Jwt from "jsonwebtoken";
|
import Jwt from "jsonwebtoken";
|
||||||
import {JITSI_URL} from "../Enum/EnvironmentVariable";
|
import { JITSI_URL } from "../Enum/EnvironmentVariable";
|
||||||
import {clientEventsEmitter} from "./ClientEventsEmitter";
|
import { clientEventsEmitter } from "./ClientEventsEmitter";
|
||||||
import {gaugeManager} from "./GaugeManager";
|
import { gaugeManager } from "./GaugeManager";
|
||||||
import {apiClientRepository} from "./ApiClientRepository";
|
import { apiClientRepository } from "./ApiClientRepository";
|
||||||
import {GroupDescriptor, UserDescriptor, ZoneEventListener} from "_Model/Zone";
|
import { GroupDescriptor, UserDescriptor, ZoneEventListener } from "_Model/Zone";
|
||||||
import Debug from "debug";
|
import Debug from "debug";
|
||||||
import {ExAdminSocketInterface} from "_Model/Websocket/ExAdminSocketInterface";
|
import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface";
|
||||||
import {WebSocket} from "uWebSockets.js";
|
import { WebSocket } from "uWebSockets.js";
|
||||||
|
|
||||||
const debug = Debug('socket');
|
const debug = Debug("socket");
|
||||||
|
|
||||||
interface AdminSocketRoomsList {
|
interface AdminSocketRoomsList {
|
||||||
[index: string]: number;
|
[index: string]: number;
|
||||||
@ -55,12 +55,11 @@ interface AdminSocketUsersList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminSocketData {
|
export interface AdminSocketData {
|
||||||
rooms: AdminSocketRoomsList,
|
rooms: AdminSocketRoomsList;
|
||||||
users: AdminSocketUsersList,
|
users: AdminSocketUsersList;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SocketManager implements ZoneEventListener {
|
export class SocketManager implements ZoneEventListener {
|
||||||
|
|
||||||
private rooms: Map<string, PusherRoom> = new Map<string, PusherRoom>();
|
private rooms: Map<string, PusherRoom> = new Map<string, PusherRoom>();
|
||||||
private sockets: Map<number, ExSocketInterface> = new Map<number, ExSocketInterface>();
|
private sockets: Map<number, ExSocketInterface> = new Map<number, ExSocketInterface>();
|
||||||
|
|
||||||
@ -78,47 +77,53 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
const adminRoomStream = apiClient.adminRoom();
|
const adminRoomStream = apiClient.adminRoom();
|
||||||
client.adminConnection = adminRoomStream;
|
client.adminConnection = adminRoomStream;
|
||||||
|
|
||||||
adminRoomStream.on('data', (message: ServerToAdminClientMessage) => {
|
adminRoomStream
|
||||||
|
.on("data", (message: ServerToAdminClientMessage) => {
|
||||||
if (message.hasUserjoinedroom()) {
|
if (message.hasUserjoinedroom()) {
|
||||||
const userJoinedRoomMessage = message.getUserjoinedroom() as UserJoinedRoomMessage;
|
const userJoinedRoomMessage = message.getUserjoinedroom() as UserJoinedRoomMessage;
|
||||||
if (!client.disconnecting) {
|
if (!client.disconnecting) {
|
||||||
client.send(JSON.stringify({
|
client.send(
|
||||||
type: 'MemberJoin',
|
JSON.stringify({
|
||||||
data: {
|
type: "MemberJoin",
|
||||||
uuid: userJoinedRoomMessage.getUuid(),
|
data: {
|
||||||
name: userJoinedRoomMessage.getName(),
|
uuid: userJoinedRoomMessage.getUuid(),
|
||||||
ipAddress: userJoinedRoomMessage.getIpaddress(),
|
name: userJoinedRoomMessage.getName(),
|
||||||
roomId: roomId,
|
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) {
|
if (!client.disconnecting) {
|
||||||
client.send(JSON.stringify({
|
this.closeWebsocketConnection(client, 1011, "Connection lost to back server");
|
||||||
type: 'MemberLeave',
|
|
||||||
data: {
|
|
||||||
uuid: userLeftRoomMessage.getUuid()
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
} else {
|
console.log("A user left");
|
||||||
throw new Error('Unexpected admin message');
|
})
|
||||||
}
|
.on("error", (err: Error) => {
|
||||||
}).on('end', () => {
|
console.error("Error in connection to back server:", err);
|
||||||
console.warn('Admin connection lost to back server');
|
if (!client.disconnecting) {
|
||||||
// Let's close the front connection if the back connection is closed. This way, we can retry connecting from the start.
|
this.closeWebsocketConnection(client, 1011, "Error while connecting to back server");
|
||||||
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 message = new AdminPusherToBackMessage();
|
const message = new AdminPusherToBackMessage();
|
||||||
message.setSubscribetoroom(roomId);
|
message.setSubscribetoroom(roomId);
|
||||||
@ -126,14 +131,14 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
adminRoomStream.write(message);
|
adminRoomStream.write(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
leaveAdminRoom(socket : ExAdminSocketInterface) {
|
leaveAdminRoom(socket: ExAdminSocketInterface) {
|
||||||
if (socket.adminConnection) {
|
if (socket.adminConnection) {
|
||||||
socket.adminConnection.end();
|
socket.adminConnection.end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getAdminSocketDataFor(roomId:string): AdminSocketData {
|
getAdminSocketDataFor(roomId: string): AdminSocketData {
|
||||||
throw new Error('Not reimplemented yet');
|
throw new Error("Not reimplemented yet");
|
||||||
/*const data:AdminSocketData = {
|
/*const data:AdminSocketData = {
|
||||||
rooms: {},
|
rooms: {},
|
||||||
users: {},
|
users: {},
|
||||||
@ -153,7 +158,6 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
async handleJoinRoom(client: ExSocketInterface): Promise<void> {
|
async handleJoinRoom(client: ExSocketInterface): Promise<void> {
|
||||||
const viewport = client.viewport;
|
const viewport = client.viewport;
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const joinRoomMessage = new JoinRoomMessage();
|
const joinRoomMessage = new JoinRoomMessage();
|
||||||
joinRoomMessage.setUseruuid(client.userUuid);
|
joinRoomMessage.setUseruuid(client.userUuid);
|
||||||
joinRoomMessage.setIpaddress(client.IPAddress);
|
joinRoomMessage.setIpaddress(client.IPAddress);
|
||||||
@ -176,46 +180,49 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
joinRoomMessage.addCharacterlayer(characterLayerMessage);
|
joinRoomMessage.addCharacterlayer(characterLayerMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Calling joinRoom");
|
||||||
console.log('Calling joinRoom')
|
|
||||||
const apiClient = await apiClientRepository.getClient(client.roomId);
|
const apiClient = await apiClientRepository.getClient(client.roomId);
|
||||||
const streamToPusher = apiClient.joinRoom();
|
const streamToPusher = apiClient.joinRoom();
|
||||||
clientEventsEmitter.emitClientJoin(client.userUuid, client.roomId);
|
clientEventsEmitter.emitClientJoin(client.userUuid, client.roomId);
|
||||||
|
|
||||||
client.backConnection = streamToPusher;
|
client.backConnection = streamToPusher;
|
||||||
|
|
||||||
streamToPusher.on('data', (message: ServerToClientMessage) => {
|
streamToPusher
|
||||||
if (message.hasRoomjoinedmessage()) {
|
.on("data", (message: ServerToClientMessage) => {
|
||||||
client.userId = (message.getRoomjoinedmessage() as RoomJoinedMessage).getCurrentuserid();
|
if (message.hasRoomjoinedmessage()) {
|
||||||
// TODO: do we need this.sockets anymore?
|
client.userId = (message.getRoomjoinedmessage() as RoomJoinedMessage).getCurrentuserid();
|
||||||
this.sockets.set(client.userId, client);
|
// TODO: do we need this.sockets anymore?
|
||||||
|
this.sockets.set(client.userId, client);
|
||||||
|
|
||||||
// If this is the first message sent, send back the viewport.
|
// If this is the first message sent, send back the viewport.
|
||||||
this.handleViewport(client, viewport);
|
this.handleViewport(client, viewport);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (message.hasRefreshroommessage()) {
|
||||||
if (!client.disconnecting) {
|
const refreshMessage: RefreshRoomMessage =
|
||||||
client.send(message.serializeBinary().buffer, true);
|
message.getRefreshroommessage() as unknown as RefreshRoomMessage;
|
||||||
}
|
this.refreshRoomData(refreshMessage.getRoomid(), refreshMessage.getVersionnumber());
|
||||||
}).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.
|
// Let's pass data over from the back to the client.
|
||||||
if (!client.disconnecting) {
|
if (!client.disconnecting) {
|
||||||
this.closeWebsocketConnection(client, 1011, 'Connection lost to back server');
|
client.send(message.serializeBinary().buffer, true);
|
||||||
}
|
}
|
||||||
console.log('A user left');
|
})
|
||||||
}).on('error', (err: Error) => {
|
.on("end", () => {
|
||||||
console.error('Error in connection to back server:', err);
|
console.warn("Connection lost to back server");
|
||||||
if (!client.disconnecting) {
|
// Let's close the front connection if the back connection is closed. This way, we can retry connecting from the start.
|
||||||
this.closeWebsocketConnection(client, 1011, 'Error while connecting to back server');
|
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();
|
const pusherToBackMessage = new PusherToBackMessage();
|
||||||
pusherToBackMessage.setJoinroommessage(joinRoomMessage);
|
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;
|
client.disconnecting = true;
|
||||||
//this.leaveRoom(client);
|
//this.leaveRoom(client);
|
||||||
//client.close();
|
//client.close();
|
||||||
@ -257,15 +264,13 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
|
|
||||||
const viewport = userMovesMessage.getViewport();
|
const viewport = userMovesMessage.getViewport();
|
||||||
if (viewport === undefined) {
|
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.
|
// 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 {
|
onEmote(emoteMessage: EmoteEventMessage, listener: ExSocketInterface): void {
|
||||||
const subMessage = new SubMessage();
|
const subMessage = new SubMessage();
|
||||||
subMessage.setEmoteeventmessage(emoteMessage);
|
subMessage.setEmoteeventmessage(emoteMessage);
|
||||||
@ -299,11 +304,16 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
try {
|
try {
|
||||||
const reportedSocket = this.sockets.get(reportPlayerMessage.getReporteduserid());
|
const reportedSocket = this.sockets.get(reportPlayerMessage.getReporteduserid());
|
||||||
if (!reportedSocket) {
|
if (!reportedSocket) {
|
||||||
throw 'reported socket user not found';
|
throw "reported socket user not found";
|
||||||
}
|
}
|
||||||
//TODO report user on admin application
|
//TODO report user on admin application
|
||||||
//todo: move to back because this fail if the reported player is in another pusher.
|
//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])
|
await adminApi.reportPlayer(
|
||||||
|
reportedSocket.userUuid,
|
||||||
|
reportPlayerMessage.getReportcomment(),
|
||||||
|
client.userUuid,
|
||||||
|
client.roomId.split("/")[2]
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('An error occurred on "handleReportMessage"');
|
console.error('An error occurred on "handleReportMessage"');
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@ -325,14 +335,14 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private searchClientByIdOrFail(userId: number): ExSocketInterface {
|
private searchClientByIdOrFail(userId: number): ExSocketInterface {
|
||||||
const client: ExSocketInterface|undefined = this.sockets.get(userId);
|
const client: ExSocketInterface | undefined = this.sockets.get(userId);
|
||||||
if (client === undefined) {
|
if (client === undefined) {
|
||||||
throw new Error("Could not find user with id " + userId);
|
throw new Error("Could not find user with id " + userId);
|
||||||
}
|
}
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
leaveRoom(socket : ExSocketInterface) {
|
leaveRoom(socket: ExSocketInterface) {
|
||||||
// leave previous room and world
|
// leave previous room and world
|
||||||
try {
|
try {
|
||||||
if (socket.roomId) {
|
if (socket.roomId) {
|
||||||
@ -340,15 +350,15 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
//user leaves room
|
//user leaves room
|
||||||
const room: PusherRoom | undefined = this.rooms.get(socket.roomId);
|
const room: PusherRoom | undefined = this.rooms.get(socket.roomId);
|
||||||
if (room) {
|
if (room) {
|
||||||
debug('Leaving room %s.', socket.roomId);
|
debug("Leaving room %s.", socket.roomId);
|
||||||
|
|
||||||
room.leave(socket);
|
room.leave(socket);
|
||||||
if (room.isEmpty()) {
|
if (room.isEmpty()) {
|
||||||
this.rooms.delete(socket.roomId);
|
this.rooms.delete(socket.roomId);
|
||||||
debug('Room %s is empty. Deleting.', socket.roomId);
|
debug("Room %s is empty. Deleting.", socket.roomId);
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
//user leave previous room
|
||||||
//Client.leave(Client.roomId);
|
//Client.leave(Client.roomId);
|
||||||
@ -356,7 +366,7 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
//delete Client.roomId;
|
//delete Client.roomId;
|
||||||
this.sockets.delete(socket.userId);
|
this.sockets.delete(socket.userId);
|
||||||
clientEventsEmitter.emitClientLeave(socket.userUuid, socket.roomId);
|
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 {
|
} finally {
|
||||||
@ -368,27 +378,27 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
|
|
||||||
async getOrCreateRoom(roomId: string): Promise<PusherRoom> {
|
async getOrCreateRoom(roomId: string): Promise<PusherRoom> {
|
||||||
//check and create new world for a room
|
//check and create new world for a room
|
||||||
let world = this.rooms.get(roomId)
|
let world = this.rooms.get(roomId);
|
||||||
if(world === undefined){
|
if (world === undefined) {
|
||||||
world = new PusherRoom(roomId, this);
|
world = new PusherRoom(roomId, this);
|
||||||
if (!world.public) {
|
if (!world.public) {
|
||||||
await this.updateRoomWithAdminData(world);
|
await this.updateRoomWithAdminData(world);
|
||||||
}
|
}
|
||||||
this.rooms.set(roomId, world);
|
this.rooms.set(roomId, world);
|
||||||
}
|
}
|
||||||
return Promise.resolve(world)
|
return Promise.resolve(world);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateRoomWithAdminData(world: PusherRoom): Promise<void> {
|
public async updateRoomWithAdminData(world: PusherRoom): Promise<void> {
|
||||||
const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug)
|
const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug);
|
||||||
world.tags = data.tags;
|
world.tags = data.tags;
|
||||||
world.policyType = Number(data.policy_type);
|
world.policyType = Number(data.policy_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
emitPlayGlobalMessage(client: ExSocketInterface, playglobalmessage: PlayGlobalMessage) {
|
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.
|
//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();
|
const pusherToBackMessage = new PusherToBackMessage();
|
||||||
pusherToBackMessage.setPlayglobalmessage(playglobalmessage);
|
pusherToBackMessage.setPlayglobalmessage(playglobalmessage);
|
||||||
@ -399,44 +409,48 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
public getWorlds(): Map<string, PusherRoom> {
|
public getWorlds(): Map<string, PusherRoom> {
|
||||||
return this.rooms;
|
return this.rooms;
|
||||||
}
|
}
|
||||||
|
|
||||||
searchClientByUuid(uuid: string): ExSocketInterface | null {
|
searchClientByUuid(uuid: string): ExSocketInterface | null {
|
||||||
for(const socket of this.sockets.values()){
|
for (const socket of this.sockets.values()) {
|
||||||
if(socket.userUuid === uuid){
|
if (socket.userUuid === uuid) {
|
||||||
return socket;
|
return socket;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public handleQueryJitsiJwtMessage(client: ExSocketInterface, queryJitsiJwtMessage: QueryJitsiJwtMessage) {
|
public handleQueryJitsiJwtMessage(client: ExSocketInterface, queryJitsiJwtMessage: QueryJitsiJwtMessage) {
|
||||||
try {
|
try {
|
||||||
const room = queryJitsiJwtMessage.getJitsiroom();
|
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.
|
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 === '') {
|
if (SECRET_JITSI_KEY === "") {
|
||||||
throw new Error('You must set the SECRET_JITSI_KEY key to the secret to generate JWT tokens for Jitsi.');
|
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
|
// Let's see if the current client has
|
||||||
const isAdmin = client.tags.includes(tag);
|
const isAdmin = client.tags.includes(tag);
|
||||||
|
|
||||||
const jwt = Jwt.sign({
|
const jwt = Jwt.sign(
|
||||||
"aud": "jitsi",
|
{
|
||||||
"iss": JITSI_ISS,
|
aud: "jitsi",
|
||||||
"sub": JITSI_URL,
|
iss: JITSI_ISS,
|
||||||
"room": room,
|
sub: JITSI_URL,
|
||||||
"moderator": isAdmin
|
room: room,
|
||||||
}, SECRET_JITSI_KEY, {
|
moderator: isAdmin,
|
||||||
expiresIn: '1d',
|
},
|
||||||
algorithm: "HS256",
|
SECRET_JITSI_KEY,
|
||||||
header:
|
{
|
||||||
{
|
expiresIn: "1d",
|
||||||
"alg": "HS256",
|
algorithm: "HS256",
|
||||||
"typ": "JWT"
|
header: {
|
||||||
}
|
alg: "HS256",
|
||||||
});
|
typ: "JWT",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const sendJitsiJwtMessage = new SendJitsiJwtMessage();
|
const sendJitsiJwtMessage = new SendJitsiJwtMessage();
|
||||||
sendJitsiJwtMessage.setJitsiroom(room);
|
sendJitsiJwtMessage.setJitsiroom(room);
|
||||||
@ -447,7 +461,7 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
|
|
||||||
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||||
} catch (e) {
|
} 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);
|
backAdminMessage.setType(type);
|
||||||
backConnection.sendAdminMessage(backAdminMessage, (error) => {
|
backConnection.sendAdminMessage(backAdminMessage, (error) => {
|
||||||
if (error !== null) {
|
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);
|
banMessage.setType(type);
|
||||||
backConnection.ban(banMessage, (error) => {
|
backConnection.ban(banMessage, (error) => {
|
||||||
if (error !== null) {
|
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.
|
* 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[] = [];
|
const characterLayerObjs: CharacterLayer[] = [];
|
||||||
for (const characterLayer of characterLayers) {
|
for (const characterLayer of characterLayers) {
|
||||||
if (characterLayer.startsWith('customCharacterTexture')) {
|
if (characterLayer.startsWith("customCharacterTexture")) {
|
||||||
const customCharacterLayerId: number = +characterLayer.substr(22);
|
const customCharacterLayerId: number = +characterLayer.substr(22);
|
||||||
for (const memberTexture of memberTextures) {
|
for (const memberTexture of memberTextures) {
|
||||||
if (memberTexture.id == customCharacterLayerId) {
|
if (memberTexture.id == customCharacterLayerId) {
|
||||||
characterLayerObjs.push({
|
characterLayerObjs.push({
|
||||||
name: characterLayer,
|
name: characterLayer,
|
||||||
url: memberTexture.url
|
url: memberTexture.url,
|
||||||
})
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
characterLayerObjs.push({
|
characterLayerObjs.push({
|
||||||
name: characterLayer,
|
name: characterLayer,
|
||||||
url: undefined
|
url: undefined,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return characterLayerObjs;
|
return characterLayerObjs;
|
||||||
@ -572,7 +589,7 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
|
|
||||||
emitInBatch(listener, subMessage);
|
emitInBatch(listener, subMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public emitWorldFullMessage(client: WebSocket) {
|
public emitWorldFullMessage(client: WebSocket) {
|
||||||
const errorMessage = new WorldFullMessage();
|
const errorMessage = new WorldFullMessage();
|
||||||
|
|
||||||
@ -594,9 +611,9 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
|
|
||||||
private refreshRoomData(roomId: string, versionNumber: number): void {
|
private refreshRoomData(roomId: string, versionNumber: number): void {
|
||||||
const room = this.rooms.get(roomId);
|
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;
|
if (!room || !room.needsUpdate(versionNumber)) return;
|
||||||
|
|
||||||
this.updateRoomWithAdminData(room);
|
this.updateRoomWithAdminData(room);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user