Merge branch 'develop' into fix/dependencies-cleanup

This commit is contained in:
Piotr Dobrowolski 2021-04-13 21:33:56 +02:00 committed by GitHub
commit 603fc0a591
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
412 changed files with 9828 additions and 13648 deletions

View file

@ -1,15 +1,26 @@
FROM thecodingmachine/workadventure-back-base:latest as builder
WORKDIR /var/www/messages
COPY --chown=docker:docker messages .
# protobuf build
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder
WORKDIR /usr/src
COPY messages .
RUN yarn install && yarn proto
FROM thecodingmachine/nodejs:12
COPY --chown=docker:docker pusher .
COPY --from=builder --chown=docker:docker /var/www/messages/generated /usr/src/app/src/Messages/generated
# typescript build
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder2
WORKDIR /usr/src
COPY pusher/yarn.lock pusher/package.json ./
RUN yarn install
COPY pusher .
COPY --from=builder /usr/src/generated src/Messages/generated
ENV NODE_ENV=production
RUN yarn run tsc
# final production image
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76
WORKDIR /usr/src
COPY pusher/yarn.lock pusher/package.json ./
COPY --from=builder2 /usr/src/dist /usr/src/dist
ENV NODE_ENV=production
RUN yarn install --production
USER node
CMD ["yarn", "run", "runprod"]

17
pusher/Dockerfile.prod Normal file
View file

@ -0,0 +1,17 @@
FROM node:12.19.0-slim
RUN mkdir -p /home/node/app && chown -R node:node /home/node/app
WORKDIR /home/node/app
USER node
ENV NODE_ENV=production
ENV DEBUG=*
COPY --chown=node:node package.json yarn.lock ./
RUN yarn install --prod --frozen-lockfile
COPY --chown=node:node ./dist/ ./dist/
EXPOSE 8080
CMD ["yarn", "run", "runprod"]

View file

@ -10,8 +10,8 @@
"runprod": "node --max-old-space-size=4096 ./dist/server.js",
"profile": "tsc && node --prof ./dist/server.js",
"test": "ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
"lint": "node_modules/.bin/eslint src/ . --ext .ts",
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts"
"lint": "DEBUG= node_modules/.bin/eslint src/ . --ext .ts",
"fix": "DEBUG= node_modules/.bin/eslint --fix src/ . --ext .ts"
},
"repository": {
"type": "git",

View file

@ -5,6 +5,7 @@ import {MapController} from "./Controller/MapController";
import {PrometheusController} from "./Controller/PrometheusController";
import {DebugController} from "./Controller/DebugController";
import {App as uwsApp} from "./Server/sifrr.server";
import {AdminController} from "./Controller/AdminController";
class App {
public app: uwsApp;
@ -13,6 +14,7 @@ class App {
public mapController: MapController;
public prometheusController: PrometheusController;
private debugController: DebugController;
private adminController: AdminController;
constructor() {
this.app = new uwsApp();
@ -23,6 +25,7 @@ class App {
this.mapController = new MapController(this.app);
this.prometheusController = new PrometheusController(this.app);
this.debugController = new DebugController(this.app);
this.adminController = new AdminController(this.app);
}
}

View file

@ -0,0 +1,137 @@
import {BaseController} from "./BaseController";
import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js";
import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable";
import {apiClientRepository} from "../Services/ApiClientRepository";
import {AdminRoomMessage, WorldFullWarningToRoomMessage, RefreshRoomPromptMessage} from "../Messages/generated/messages_pb";
export class AdminController extends BaseController{
constructor(private App : TemplatedApp) {
super();
this.App = App;
this.receiveGlobalMessagePrompt();
this.receiveRoomEditionPrompt();
}
receiveRoomEditionPrompt() {
this.App.options("/room/refresh", (res: HttpResponse, req: HttpRequest) => {
this.addCorsHeaders(res);
res.end();
});
// eslint-disable-next-line @typescript-eslint/no-misused-promises
this.App.post("/room/refresh", async (res: HttpResponse, req: HttpRequest) => {
res.onAborted(() => {
console.warn('/message request was aborted');
})
const token = req.getHeader('admin-token');
const body = await res.json();
if (token !== ADMIN_API_TOKEN) {
console.error('Admin access refused for token: '+token)
res.writeStatus("401 Unauthorized").end('Incorrect token');
return;
}
try {
if (typeof body.roomId !== 'string') {
throw 'Incorrect roomId parameter'
}
const roomId: string = body.roomId;
await apiClientRepository.getClient(roomId).then((roomClient) =>{
return new Promise((res, rej) => {
const roomMessage = new RefreshRoomPromptMessage();
roomMessage.setRoomid(roomId);
roomClient.sendRefreshRoomPrompt(roomMessage, (err) => {
err ? rej(err) : res();
});
});
});
} catch (err) {
this.errorToResponse(err, res);
return;
}
res.writeStatus("200");
res.end('ok');
});
}
receiveGlobalMessagePrompt() {
this.App.options("/message", (res: HttpResponse, req: HttpRequest) => {
this.addCorsHeaders(res);
res.end();
});
// eslint-disable-next-line @typescript-eslint/no-misused-promises
this.App.post("/message", async (res: HttpResponse, req: HttpRequest) => {
res.onAborted(() => {
console.warn('/message request was aborted');
})
const token = req.getHeader('admin-token');
const body = await res.json();
if (token !== ADMIN_API_TOKEN) {
console.error('Admin access refused for token: '+token)
res.writeStatus("401 Unauthorized").end('Incorrect token');
return;
}
try {
if (typeof body.text !== 'string') {
throw 'Incorrect text parameter'
}
if (body.type !== 'capacity' && body.type !== 'message') {
throw 'Incorrect type parameter'
}
if (!body.targets || typeof body.targets !== 'object') {
throw 'Incorrect targets parameter'
}
const text: string = body.text;
const type: string = body.type;
const targets: string[] = body.targets;
await Promise.all(targets.map((roomId) => {
return apiClientRepository.getClient(roomId).then((roomClient) =>{
return new Promise((res, rej) => {
if (type === 'message') {
const roomMessage = new AdminRoomMessage();
roomMessage.setMessage(text);
roomMessage.setRoomid(roomId);
roomClient.sendAdminMessageToRoom(roomMessage, (err) => {
err ? rej(err) : res();
});
} else if (type === 'capacity') {
const roomMessage = new WorldFullWarningToRoomMessage();
roomMessage.setRoomid(roomId);
roomClient.sendWorldFullWarningToRoom(roomMessage, (err) => {
err ? rej(err) : res();
});
}
});
});
}));
} catch (err) {
this.errorToResponse(err, res);
return;
}
res.writeStatus("200");
this.addCorsHeaders(res);
res.end('ok');
});
}
}

View file

@ -56,14 +56,12 @@ export class AuthenticateController extends BaseController {
worldSlug,
roomSlug,
mapUrlStart,
organizationMemberToken,
textures
}));
} catch (e) {
console.error("An error happened", e)
res.writeStatus(e.status || "500 Internal Server Error");
this.addCorsHeaders(res);
res.end('An error happened');
this.errorToResponse(e, res);
}

View file

@ -1,5 +1,4 @@
import {HttpRequest, HttpResponse} from "uWebSockets.js";
import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable";
import {HttpResponse} from "uWebSockets.js";
export class BaseController {
@ -8,4 +7,34 @@ export class BaseController {
res.writeHeader('access-control-allow-methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.writeHeader('access-control-allow-origin', '*');
}
/**
* Turns any exception into a HTTP response (and logs the error)
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected errorToResponse(e: any, res: HttpResponse): void {
if (e && e.message) {
let url = e?.config?.url;
if (url !== undefined) {
url = ' for URL: '+url;
} else {
url = '';
}
console.error('ERROR: '+e.message+url);
} else if (typeof(e) === 'string') {
console.error(e);
}
if (e.stack) {
console.error(e.stack);
}
if (e.response) {
res.writeStatus(e.response.status+" "+e.response.statusText);
this.addCorsHeaders(res);
res.end("An error occurred: "+e.response.status+" "+e.response.statusText);
} else {
res.writeStatus("500 Internal Server Error")
this.addCorsHeaders(res);
res.end("An error occurred");
}
}
}

View file

@ -1,5 +1,5 @@
import {CharacterLayer, ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.."
import {GameRoomPolicyTypes} from "../Model/PusherRoom";
import {GameRoomPolicyTypes, PusherRoom} from "../Model/PusherRoom";
import {PointInterface} from "../Model/Websocket/PointInterface";
import {
SetPlayerDetailsMessage,
@ -12,7 +12,7 @@ import {
WebRtcSignalToServerMessage,
PlayGlobalMessage,
ReportPlayerMessage,
QueryJitsiJwtMessage, SendUserMessage, ServerToClientMessage
QueryJitsiJwtMessage, SendUserMessage, ServerToClientMessage, CompanionMessage
} from "../Messages/generated/messages_pb";
import {UserMovesMessage} from "../Messages/generated/messages_pb";
import {TemplatedApp} from "uWebSockets.js"
@ -20,11 +20,11 @@ import {parse} from "query-string";
import {jwtTokenManager} from "../Services/JWTTokenManager";
import {adminApi, CharacterTexture, FetchMemberDataByUuidResponse} from "../Services/AdminApi";
import {SocketManager, socketManager} from "../Services/SocketManager";
import {emitInBatch} from "../Services/IoSocketHelpers";
import {clientEventsEmitter} from "../Services/ClientEventsEmitter";
import {emitError, emitInBatch} from "../Services/IoSocketHelpers";
import {ADMIN_API_TOKEN, ADMIN_API_URL, SOCKET_IDLE_TIMER} from "../Enum/EnvironmentVariable";
import {Zone} from "_Model/Zone";
import {ExAdminSocketInterface} from "_Model/Websocket/ExAdminSocketInterface";
import {v4} from "uuid";
export class IoSocketController {
private nextUserId: number = 1;
@ -64,22 +64,6 @@ export class IoSocketController {
ws.disconnecting = false;
socketManager.handleAdminRoom(ws as ExAdminSocketInterface, ws.roomId as string);
/*ws.send('Data:'+JSON.stringify(socketManager.getAdminSocketDataFor(ws.roomId as string)));
ws.clientJoinCallback = (clientUUid: string, roomId: string) => {
const wsroomId = ws.roomId as string;
if(wsroomId === roomId) {
ws.send('MemberJoin:'+clientUUid+';'+roomId);
}
};
ws.clientLeaveCallback = (clientUUid: string, roomId: string) => {
const wsroomId = ws.roomId as string;
if(wsroomId === roomId) {
ws.send('MemberLeave:'+clientUUid+';'+roomId);
}
};
clientEventsEmitter.registerToClientJoin(ws.clientJoinCallback);
clientEventsEmitter.registerToClientLeave(ws.clientLeaveCallback);*/
},
message: (ws, arrayBuffer, isBinary): void => {
try {
@ -91,18 +75,11 @@ export class IoSocketController {
if(message.event === 'user-message') {
const messageToEmit = (message.message as { message: string, type: string, userUuid: string });
switch (message.message.type) {
case 'ban': {
socketManager.emitSendUserMessage(messageToEmit.userUuid, messageToEmit.message, roomId);
break;
}
case 'banned': {
socketManager.emitBan(messageToEmit.userUuid, messageToEmit.message, roomId);
break;
}
default: {
break;
}
if(messageToEmit.type === 'banned'){
socketManager.emitBan(messageToEmit.userUuid, messageToEmit.message, messageToEmit.type, ws.roomId as string);
}
if(messageToEmit.type === 'ban') {
socketManager.emitSendUserMessage(messageToEmit.userUuid, messageToEmit.message, messageToEmit.type, ws.roomId as string);
}
}
}catch (err) {
@ -113,7 +90,6 @@ export class IoSocketController {
const Client = (ws as ExAdminSocketInterface);
try {
Client.disconnecting = true;
//leave room
socketManager.leaveAdminRoom(Client);
} catch (e) {
console.error('An error occurred on admin "disconnect"');
@ -132,7 +108,6 @@ export class IoSocketController {
maxBackpressure: 65536, // Maximum 64kB of data in the buffer.
//idleTimeout: 10,
upgrade: (res, req, context) => {
//console.log('An Http connection wants to become WebSocket, URL: ' + req.getUrl() + '!');
(async () => {
/* Keep track of abortions */
const upgradeAborted = {aborted: false};
@ -148,6 +123,7 @@ export class IoSocketController {
const websocketKey = req.getHeader('sec-websocket-key');
const websocketProtocol = req.getHeader('sec-websocket-protocol');
const websocketExtensions = req.getHeader('sec-websocket-extensions');
const IPAddress = req.getHeader('x-forwarded-for');
const roomId = query.roomId;
if (typeof roomId !== 'string') {
@ -162,6 +138,14 @@ export class IoSocketController {
const left = Number(query.left);
const right = Number(query.right);
const name = query.name;
let companion: CompanionMessage|undefined = undefined;
if (typeof query.companion === 'string') {
companion = new CompanionMessage();
companion.setName(query.companion);
}
if (typeof name !== 'string') {
throw new Error('Expecting name');
}
@ -176,25 +160,50 @@ export class IoSocketController {
characterLayers = [ characterLayers ];
}
const userUuid = await jwtTokenManager.getUserUuidFromToken(token);
const userUuid = await jwtTokenManager.getUserUuidFromToken(token, IPAddress, roomId);
let memberTags: string[] = [];
let memberMessages: unknown;
let memberTextures: CharacterTexture[] = [];
const room = await socketManager.getOrCreateRoom(roomId);
// TODO: make sure the room isFull is ported in the back part.
/*if(room.isFull){
throw new Error('Room is full');
}*/
if (ADMIN_API_URL) {
try {
const userData = await adminApi.fetchMemberDataByUuid(userUuid);
//console.log('USERDATA', userData)
let userData : FetchMemberDataByUuidResponse = {
uuid: v4(),
tags: [],
textures: [],
messages: [],
anonymous: true
};
try {
userData = await adminApi.fetchMemberDataByUuid(userUuid, roomId);
}catch (err){
if (err?.response?.status == 404) {
// If we get an HTTP 404, the token is invalid. Let's perform an anonymous login!
console.warn('Cannot find user with uuid "'+userUuid+'". Performing an anonymous login instead.');
} else if(err?.response?.status == 403) {
// If we get an HTTP 404, the world is full. We need to broadcast a special error to the client.
// we finish immediatly the upgrade then we will close the socket as soon as it starts opening.
res.upgrade({
rejected: true,
}, websocketKey,
websocketProtocol,
websocketExtensions,
context);
return;
}else{
throw err;
}
}
memberMessages = userData.messages;
memberTags = userData.tags;
memberTextures = userData.textures;
if (!room.anonymous && room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && !room.canAccess(memberTags)) {
if (!room.public && room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && (userData.anonymous === true || !room.canAccess(memberTags))) {
throw new Error('No correct tags')
}
//console.log('access granted for user '+userUuid+' and room '+roomId);
if (!room.public && room.policyType === GameRoomPolicyTypes.MEMBERS_ONLY_POLICY && userData.anonymous === true) {
throw new Error('No correct member')
}
} catch (e) {
console.log('access not granted for user '+userUuid+' and room '+roomId);
console.error(e);
@ -217,9 +226,12 @@ export class IoSocketController {
url,
token,
userUuid,
IPAddress,
roomId,
name,
companion,
characterLayers: characterLayerObjs,
messages: memberMessages,
tags: memberTags,
textures: memberTextures,
position: {
@ -246,7 +258,6 @@ export class IoSocketController {
console.log(e.message);
res.writeStatus("401 Unauthorized").end(e.message);
} else {
console.log(e);
res.writeStatus("500 Internal Server Error").end('An error occurred');
}
return;
@ -255,32 +266,30 @@ export class IoSocketController {
},
/* Handlers */
open: (ws) => {
if(ws.rejected === true) {
socketManager.emitWorldFullMessage(ws);
ws.close();
}
// Let's join the room
const client = this.initClient(ws); //todo: into the upgrade instead?
const client = this.initClient(ws);
socketManager.handleJoinRoom(client);
//get data information and show messages
if (ADMIN_API_URL) {
adminApi.fetchMemberDataByUuid(client.userUuid).then((res: FetchMemberDataByUuidResponse) => {
if (!res.messages) {
return;
if (client.messages && Array.isArray(client.messages)) {
client.messages.forEach((c: unknown) => {
const messageToSend = c as { type: string, message: string };
const sendUserMessage = new SendUserMessage();
sendUserMessage.setType(messageToSend.type);
sendUserMessage.setMessage(messageToSend.message);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendusermessage(sendUserMessage);
if (!client.disconnecting) {
client.send(serverToClientMessage.serializeBinary().buffer, true);
}
res.messages.forEach((c: unknown) => {
const messageToSend = c as { type: string, message: string };
const sendUserMessage = new SendUserMessage();
sendUserMessage.setType(messageToSend.type);
sendUserMessage.setMessage(messageToSend.message);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendusermessage(sendUserMessage);
if (!client.disconnecting) {
client.send(serverToClientMessage.serializeBinary().buffer, true);
}
});
}).catch((err) => {
console.error('fetchMemberDataByUuid => err', err);
});
}
},
@ -336,6 +345,7 @@ export class IoSocketController {
client.userId = this.nextUserId;
this.nextUserId++;
client.userUuid = ws.userUuid;
client.IPAddress = ws.IPAddress;
client.token = ws.token;
client.batchedMessages = new BatchMessage();
client.batchTimeout = null;
@ -344,10 +354,12 @@ export class IoSocketController {
}
client.disconnecting = false;
client.messages = ws.messages;
client.name = ws.name;
client.tags = ws.tags;
client.textures = ws.textures;
client.characterLayers = ws.characterLayers;
client.companion = ws.companion;
client.roomId = ws.roomId;
client.listenedZones = new Set<Zone>();
return client;

View file

@ -59,10 +59,7 @@ export class MapController extends BaseController{
this.addCorsHeaders(res);
res.end(JSON.stringify(mapDetails));
} catch (e) {
console.error(e.message || e);
res.writeStatus("500 Internal Server Error")
this.addCorsHeaders(res);
res.end("An error occurred");
this.errorToResponse(e, res);
}
})();

View file

@ -1,5 +1,4 @@
const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY";
const URL_ROOM_STARTED = "/Floor0/floor0.json";
const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIMUM_DISTANCE) : 64;
const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48;
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == 'true' : false;
@ -16,7 +15,6 @@ export const SOCKET_IDLE_TIMER = parseInt(process.env.SOCKET_IDLE_TIMER as strin
export {
SECRET_KEY,
URL_ROOM_STARTED,
MINIMUM_DISTANCE,
API_URL,
ADMIN_API_URL,

View file

@ -13,21 +13,22 @@ export enum GameRoomPolicyTypes {
export class PusherRoom {
private readonly positionNotifier: PositionDispatcher;
public readonly anonymous: boolean;
public readonly public: boolean;
public tags: string[];
public policyType: GameRoomPolicyTypes;
public readonly roomSlug: string;
public readonly worldSlug: string = '';
public readonly organizationSlug: string = '';
private versionNumber: number = 1;
constructor(public readonly roomId: string,
private socketListener: ZoneEventListener)
{
this.anonymous = isRoomAnonymous(roomId);
this.public = isRoomAnonymous(roomId);
this.tags = [];
this.policyType = GameRoomPolicyTypes.ANONYMUS_POLICY;
if (this.anonymous) {
if (this.public) {
this.roomSlug = extractRoomSlugPublicRoomId(this.roomId);
} else {
const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId(this.roomId);
@ -55,4 +56,13 @@ export class PusherRoom {
public isEmpty(): boolean {
return this.positionNotifier.isEmpty();
}
public needsUpdate(versionNumber: number): boolean {
if (this.versionNumber < versionNumber) {
this.versionNumber = versionNumber;
return true;
} else {
return false;
}
}
}

View file

@ -3,6 +3,7 @@ import {Identificable} from "./Identificable";
import {ViewportInterface} from "_Model/Websocket/ViewportMessage";
import {
BatchMessage,
CompanionMessage,
PusherToBackMessage,
ServerToClientMessage,
SubMessage
@ -24,10 +25,12 @@ export interface ExSocketInterface extends WebSocket, Identificable {
roomId: string;
//userId: number; // A temporary (autoincremented) identifier for this user
userUuid: string; // A unique identifier for this user
IPAddress: string; // IP address
name: string;
characterLayers: CharacterLayer[];
position: PointInterface;
viewport: ViewportInterface;
companion?: CompanionMessage;
/**
* Pushes an event that will be sent in the next batch of events
*/
@ -35,6 +38,7 @@ export interface ExSocketInterface extends WebSocket, Identificable {
batchedMessages: BatchMessage;
batchTimeout: NodeJS.Timeout|null;
disconnecting: boolean,
messages: unknown,
tags: string[],
textures: CharacterTexture[],
backConnection: BackConnection,

View file

@ -5,7 +5,8 @@ import {
CharacterLayerMessage, GroupLeftZoneMessage, GroupUpdateMessage, GroupUpdateZoneMessage,
PointMessage, PositionMessage, UserJoinedMessage,
UserJoinedZoneMessage, UserLeftZoneMessage, UserMovedMessage,
ZoneMessage
ZoneMessage,
CompanionMessage
} from "../Messages/generated/messages_pb";
import * as messages_pb from "../Messages/generated/messages_pb";
import {ClientReadableStream} from "grpc";
@ -30,7 +31,7 @@ export type MovesCallback = (thing: Movable, position: PositionInterface, listen
export type LeavesCallback = (thing: Movable, listener: User) => void;*/
export class UserDescriptor {
private constructor(public readonly userId: number, private name: string, private characterLayers: CharacterLayerMessage[], private position: PositionMessage) {
private constructor(public readonly userId: number, private name: string, private characterLayers: CharacterLayerMessage[], private position: PositionMessage, private companion?: CompanionMessage) {
if (!Number.isInteger(this.userId)) {
throw new Error('UserDescriptor.userId is not an integer: '+this.userId);
}
@ -41,7 +42,7 @@ export class UserDescriptor {
if (position === undefined) {
throw new Error('Missing position');
}
return new UserDescriptor(message.getUserid(), message.getName(), message.getCharacterlayersList(), position);
return new UserDescriptor(message.getUserid(), message.getName(), message.getCharacterlayersList(), position, message.getCompanion());
}
public update(userMovedMessage: UserMovedMessage) {
@ -59,6 +60,7 @@ export class UserDescriptor {
userJoinedMessage.setName(this.name);
userJoinedMessage.setCharacterlayersList(this.characterLayers);
userJoinedMessage.setPosition(this.position);
userJoinedMessage.setCompanion(this.companion)
return userJoinedMessage;
}

View file

@ -1,6 +1,6 @@
import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable";
import Axios from "axios";
import {v4} from "uuid";
import {GameRoomPolicyTypes} from "_Model/PusherRoom";
export interface AdminApiData {
organizationSlug: string
@ -14,6 +14,18 @@ export interface AdminApiData {
textures: CharacterTexture[]
}
export interface MapDetailsData {
roomSlug: string,
mapUrl: string,
policy_type: GameRoomPolicyTypes,
tags: string[],
}
export interface AdminBannedData {
is_banned: boolean,
message: string
}
export interface CharacterTexture {
id: number,
level: number,
@ -26,13 +38,14 @@ export interface FetchMemberDataByUuidResponse {
tags: string[];
textures: CharacterTexture[];
messages: unknown[];
anonymous?: boolean;
}
class AdminApi {
async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise<AdminApiData> {
async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise<MapDetailsData> {
if (!ADMIN_API_URL) {
return Promise.reject('No admin backoffice set!');
return Promise.reject(new Error('No admin backoffice set!'));
}
const params: { organizationSlug: string, worldSlug: string, roomSlug?: string } = {
@ -53,34 +66,19 @@ class AdminApi {
return res.data;
}
async fetchMemberDataByUuid(uuid: string): Promise<FetchMemberDataByUuidResponse> {
async fetchMemberDataByUuid(uuid: string, roomId: string): Promise<FetchMemberDataByUuidResponse> {
if (!ADMIN_API_URL) {
return Promise.reject('No admin backoffice set!');
}
try {
const res = await Axios.get(ADMIN_API_URL+'/api/membership/'+uuid,
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
)
return res.data;
} catch (e) {
if (e?.response?.status == 404) {
// If we get an HTTP 404, the token is invalid. Let's perform an anonymous login!
console.warn('Cannot find user with uuid "'+uuid+'". Performing an anonymous login instead.');
return {
uuid: v4(),
tags: [],
textures: [],
messages: [],
}
} else {
throw e;
}
return Promise.reject(new Error('No admin backoffice set!'));
}
const res = await Axios.get(ADMIN_API_URL+'/api/room/access',
{ params: {uuid, roomId}, headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
)
return res.data;
}
async fetchMemberDataByToken(organizationMemberToken: string): Promise<AdminApiData> {
if (!ADMIN_API_URL) {
return Promise.reject('No admin backoffice set!');
return Promise.reject(new Error('No admin backoffice set!'));
}
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
const res = await Axios.get(ADMIN_API_URL+'/api/login-url/'+organizationMemberToken,
@ -91,7 +89,7 @@ class AdminApi {
async fetchCheckUserByToken(organizationMemberToken: string): Promise<AdminApiData> {
if (!ADMIN_API_URL) {
return Promise.reject('No admin backoffice set!');
return Promise.reject(new Error('No admin backoffice set!'));
}
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
const res = await Axios.get(ADMIN_API_URL+'/api/check-user/'+organizationMemberToken,
@ -100,16 +98,29 @@ class AdminApi {
return res.data;
}
reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string) {
reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string, reportWorldSlug: string) {
return Axios.post(`${ADMIN_API_URL}/api/report`, {
reportedUserUuid,
reportedUserComment,
reporterUserUuid,
reportWorldSlug
},
{
headers: {"Authorization": `${ADMIN_API_TOKEN}`}
});
}
async verifyBanUser(organizationMemberToken: string, ipAddress: string, organization: string, world: string): Promise<AdminBannedData> {
if (!ADMIN_API_URL) {
return Promise.reject(new Error('No admin backoffice set!'));
}
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
return Axios.get(ADMIN_API_URL + '/api/check-moderate-user/'+organization+'/'+world+'?ipAddress='+ipAddress+'&token='+organizationMemberToken,
{headers: {"Authorization": `${ADMIN_API_TOKEN}`}}
).then((data) => {
return data.data;
});
}
}
export const adminApi = new AdminApi();

View file

@ -13,8 +13,7 @@ const debug = Debug('apiClientRespository');
class ApiClientRepository {
private roomManagerClients: RoomManagerClient[] = [];
public constructor(private apiUrls: string[]) {
}
public constructor(private apiUrls: string[]) {}
public async getClient(roomId: string): Promise<RoomManagerClient> {
const array = new Uint32Array(crypto.createHash('md5').update(roomId).digest());

View file

@ -1,5 +1,6 @@
import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
import {BatchMessage, ErrorMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb";
import {WebSocket} from "uWebSockets.js";
export function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void {
socket.batchedMessages.addPayload(payload);
@ -20,7 +21,7 @@ export function emitInBatch(socket: ExSocketInterface, payload: SubMessage): voi
}
}
export function emitError(Client: ExSocketInterface, message: string): void {
export function emitError(Client: WebSocket, message: string): void {
const errorMessage = new ErrorMessage();
errorMessage.setMessage(message);

View file

@ -2,7 +2,7 @@ import {ADMIN_API_URL, ALLOW_ARTILLERY, SECRET_KEY} from "../Enum/EnvironmentVar
import {uuid} from "uuidv4";
import Jwt from "jsonwebtoken";
import {TokenInterface} from "../Controller/AuthenticateController";
import {adminApi, AdminApiData} from "../Services/AdminApi";
import {adminApi, AdminBannedData} from "../Services/AdminApi";
class JWTTokenManager {
@ -10,7 +10,7 @@ class JWTTokenManager {
return Jwt.sign({userUuid: userUuid}, SECRET_KEY, {expiresIn: '200d'}); //todo: add a mechanic to refresh or recreate token
}
public async getUserUuidFromToken(token: unknown): Promise<string> {
public async getUserUuidFromToken(token: unknown, ipAddress?: string, room?: string): Promise<string> {
if (!token) {
throw new Error('An authentication error happened, a user tried to connect without a token.');
@ -50,14 +50,22 @@ class JWTTokenManager {
if (ADMIN_API_URL) {
//verify user in admin
adminApi.fetchCheckUserByToken(tokenInterface.userUuid).then(() => {
resolve(tokenInterface.userUuid);
}).catch((err) => {
//anonymous user
if(err.response && err.response.status && err.response.status === 404){
let promise = new Promise((resolve) => resolve());
if(ipAddress && room) {
promise = this.verifyBanUser(tokenInterface.userUuid, ipAddress, room);
}
promise.then(() => {
adminApi.fetchCheckUserByToken(tokenInterface.userUuid).then(() => {
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);
});
} else {
@ -67,6 +75,27 @@ class JWTTokenManager {
});
}
private verifyBanUser(userUuid: string, ipAddress: string, room: string): Promise<AdminBannedData> {
const parts = room.split('/');
if (parts.length < 3 || parts[0] !== '@') {
return Promise.resolve({
is_banned: false,
message: ''
});
}
const organization = parts[1];
const world = parts[2];
return adminApi.verifyBanUser(userUuid, ipAddress, organization, world).then((data: AdminBannedData) => {
if (data && data.is_banned) {
throw new Error('User was banned');
}
return data;
}).catch((err) => {
throw err;
});
}
private isValidToken(token: object): token is TokenInterface {
return !(typeof((token as TokenInterface).userUuid) !== 'string');
}

View file

@ -2,54 +2,41 @@ import {PusherRoom} from "../Model/PusherRoom";
import {CharacterLayer, ExSocketInterface} from "../Model/Websocket/ExSocketInterface";
import {
GroupDeleteMessage,
GroupUpdateMessage,
ItemEventMessage,
ItemStateMessage,
PlayGlobalMessage,
PointMessage,
PositionMessage,
RoomJoinedMessage,
ServerToClientMessage,
SetPlayerDetailsMessage,
SilentMessage,
SubMessage,
ReportPlayerMessage,
UserJoinedMessage,
UserLeftMessage,
UserMovedMessage,
UserMovesMessage,
ViewportMessage,
WebRtcDisconnectMessage,
WebRtcSignalToClientMessage,
WebRtcSignalToServerMessage,
WebRtcStartMessage,
QueryJitsiJwtMessage,
SendJitsiJwtMessage,
SendUserMessage,
JoinRoomMessage,
CharacterLayerMessage,
PusherToBackMessage,
WorldFullMessage,
AdminPusherToBackMessage,
ServerToAdminClientMessage, AdminMessage, BanMessage
ServerToAdminClientMessage,
UserJoinedRoomMessage, UserLeftRoomMessage, AdminMessage, BanMessage, RefreshRoomMessage
} from "../Messages/generated/messages_pb";
import {PointInterface} from "../Model/Websocket/PointInterface";
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
import {cpuTracker} from "./CpuTracker";
import {GROUP_RADIUS, JITSI_ISS, MINIMUM_DISTANCE, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
import {Movable} from "../Model/Movable";
import {PositionInterface} from "../Model/PositionInterface";
import {JITSI_ISS, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
import {adminApi, CharacterTexture} from "./AdminApi";
import Direction = PositionMessage.Direction;
import {emitError, emitInBatch} from "./IoSocketHelpers";
import {emitInBatch} from "./IoSocketHelpers";
import Jwt from "jsonwebtoken";
import {JITSI_URL} from "../Enum/EnvironmentVariable";
import {clientEventsEmitter} from "./ClientEventsEmitter";
import {gaugeManager} from "./GaugeManager";
import {apiClientRepository} from "./ApiClientRepository";
import {ServiceError} from "grpc";
import {GroupDescriptor, UserDescriptor, ZoneEventListener} from "_Model/Zone";
import Debug from "debug";
import {ExAdminSocketInterface} from "_Model/Websocket/ExAdminSocketInterface";
import {WebSocket} from "uWebSockets.js";
const debug = Debug('socket');
@ -66,7 +53,8 @@ export interface AdminSocketData {
}
export class SocketManager implements ZoneEventListener {
private Worlds: 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>();
constructor() {
@ -79,23 +67,33 @@ export class SocketManager implements ZoneEventListener {
}
async handleAdminRoom(client: ExAdminSocketInterface, roomId: string): Promise<void> {
console.log('Calling adminRoom')
const apiClient = await apiClientRepository.getClient(roomId);
const adminRoomStream = apiClient.adminRoom();
client.adminConnection = adminRoomStream;
adminRoomStream.on('data', (message: ServerToAdminClientMessage) => {
if (message.hasUseruuidjoinedroom()) {
const userUuid = message.getUseruuidjoinedroom();
if (message.hasUserjoinedroom()) {
const userJoinedRoomMessage = message.getUserjoinedroom() as UserJoinedRoomMessage;
if (!client.disconnecting) {
client.send('MemberJoin:'+userUuid+';'+roomId);
client.send(JSON.stringify({
type: 'MemberJoin',
data: {
uuid: userJoinedRoomMessage.getUuid(),
name: userJoinedRoomMessage.getName(),
ipAddress: userJoinedRoomMessage.getIpaddress(),
roomId: roomId,
}
}));
}
} else if (message.hasUseruuidleftroom()) {
const userUuid = message.getUseruuidleftroom();
} else if (message.hasUserleftroom()) {
const userLeftRoomMessage = message.getUserleftroom() as UserLeftRoomMessage;
if (!client.disconnecting) {
client.send('MemberLeave:'+userUuid+';'+roomId);
client.send(JSON.stringify({
type: 'MemberLeave',
data: {
uuid: userLeftRoomMessage.getUuid()
}
}));
}
} else {
throw new Error('Unexpected admin message');
@ -145,15 +143,18 @@ export class SocketManager implements ZoneEventListener {
}
async handleJoinRoom(client: ExSocketInterface): Promise<void> {
const position = client.position;
const viewport = client.viewport;
try {
const joinRoomMessage = new JoinRoomMessage();
joinRoomMessage.setUseruuid(client.userUuid);
joinRoomMessage.setIpaddress(client.IPAddress);
joinRoomMessage.setRoomid(client.roomId);
joinRoomMessage.setName(client.name);
joinRoomMessage.setPositionmessage(ProtobufUtils.toPositionMessage(client.position));
joinRoomMessage.setTagList(client.tags);
joinRoomMessage.setCompanion(client.companion);
for (const characterLayer of client.characterLayers) {
const characterLayerMessage = new CharacterLayerMessage();
characterLayerMessage.setName(characterLayer.name);
@ -168,6 +169,7 @@ export class SocketManager implements ZoneEventListener {
console.log('Calling joinRoom')
const apiClient = await apiClientRepository.getClient(client.roomId);
const streamToPusher = apiClient.joinRoom();
clientEventsEmitter.emitClientJoin(client.userUuid, client.roomId);
client.backConnection = streamToPusher;
@ -180,6 +182,11 @@ export class SocketManager implements ZoneEventListener {
// If this is the first message sent, send back the viewport.
this.handleViewport(client, viewport);
}
if (message.hasRefreshroommessage()) {
const refreshMessage:RefreshRoomMessage = message.getRefreshroommessage() as unknown as RefreshRoomMessage;
this.refreshRoomData(refreshMessage.getRoomid(), refreshMessage.getVersionnumber())
}
// Let's pass data over from the back to the client.
if (!client.disconnecting) {
@ -219,7 +226,7 @@ export class SocketManager implements ZoneEventListener {
try {
client.viewport = viewport;
const world = this.Worlds.get(client.roomId);
const world = this.rooms.get(client.roomId);
if (!world) {
console.error("In SET_VIEWPORT, could not find world with id '", client.roomId, "'");
return;
@ -266,31 +273,6 @@ export class SocketManager implements ZoneEventListener {
pusherToBackMessage.setItemeventmessage(itemEventMessage);
client.backConnection.write(pusherToBackMessage);
/*const itemEvent = ProtobufUtils.toItemEvent(itemEventMessage);
try {
const world = this.Worlds.get(ws.roomId);
if (!world) {
console.error("Could not find world with id '", ws.roomId, "'");
return;
}
const subMessage = new SubMessage();
subMessage.setItemeventmessage(itemEventMessage);
// Let's send the event without using the SocketIO room.
for (const user of world.getUsers().values()) {
const client = this.searchClientByIdOrFail(user.id);
//client.emit(SocketIoEvent.ITEM_EVENT, itemEvent);
emitInBatch(client, subMessage);
}
world.setItemState(itemEvent.itemId, itemEvent.state);
} catch (e) {
console.error('An error occurred on "item_event"');
console.error(e);
}*/
}
async handleReportMessage(client: ExSocketInterface, reportPlayerMessage: ReportPlayerMessage) {
@ -300,7 +282,7 @@ export class SocketManager implements ZoneEventListener {
throw 'reported socket user not found';
}
//TODO report user on admin application
await adminApi.reportPlayer(reportedSocket.userUuid, reportPlayerMessage.getReportcomment(), client.userUuid)
await adminApi.reportPlayer(reportedSocket.userUuid, reportPlayerMessage.getReportcomment(), client.userUuid, client.roomId.split('/')[2])
} catch (e) {
console.error('An error occurred on "handleReportMessage"');
console.error(e);
@ -312,25 +294,6 @@ export class SocketManager implements ZoneEventListener {
pusherToBackMessage.setWebrtcsignaltoservermessage(data);
socket.backConnection.write(pusherToBackMessage);
//send only at user
/*const client = this.sockets.get(data.getReceiverid());
if (client === undefined) {
console.warn("While exchanging a WebRTC signal: client with id ", data.getReceiverid(), " does not exist. This might be a race condition.");
return;
}
const webrtcSignalToClient = new WebRtcSignalToClientMessage();
webrtcSignalToClient.setUserid(socket.userId);
webrtcSignalToClient.setSignal(data.getSignal());
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setWebrtcsignaltoclientmessage(webrtcSignalToClient);
if (!client.disconnecting) {
client.send(serverToClientMessage.serializeBinary().buffer, true);
}*/
}
emitScreenSharing(socket: ExSocketInterface, data: WebRtcSignalToServerMessage): void {
@ -338,24 +301,6 @@ export class SocketManager implements ZoneEventListener {
pusherToBackMessage.setWebrtcscreensharingsignaltoservermessage(data);
socket.backConnection.write(pusherToBackMessage);
//send only at user
/*const client = this.sockets.get(data.getReceiverid());
if (client === undefined) {
console.warn("While exchanging a WEBRTC_SCREEN_SHARING signal: client with id ", data.getReceiverid(), " does not exist. This might be a race condition.");
return;
}
const webrtcSignalToClient = new WebRtcSignalToClientMessage();
webrtcSignalToClient.setUserid(socket.userId);
webrtcSignalToClient.setSignal(data.getSignal());
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setWebrtcscreensharingsignaltoclientmessage(webrtcSignalToClient);
if (!client.disconnecting) {
client.send(serverToClientMessage.serializeBinary().buffer, true);
}*/
}
private searchClientByIdOrFail(userId: number): ExSocketInterface {
@ -372,12 +317,12 @@ export class SocketManager implements ZoneEventListener {
if (socket.roomId) {
try {
//user leaves room
const room: PusherRoom | undefined = this.Worlds.get(socket.roomId);
const room: PusherRoom | undefined = this.rooms.get(socket.roomId);
if (room) {
debug('Leaving room %s.', socket.roomId);
room.leave(socket);
if (room.isEmpty()) {
this.Worlds.delete(socket.roomId);
this.rooms.delete(socket.roomId);
debug('Room %s is empty. Deleting.', socket.roomId);
}
} else {
@ -401,83 +346,23 @@ export class SocketManager implements ZoneEventListener {
async getOrCreateRoom(roomId: string): Promise<PusherRoom> {
//check and create new world for a room
let world = this.Worlds.get(roomId)
let world = this.rooms.get(roomId)
if(world === undefined){
world = new PusherRoom(
roomId,
this
/* (user: User, group: Group) => this.joinWebRtcRoom(user, group),
(user: User, group: Group) => this.disConnectedUser(user, group),
MINIMUM_DISTANCE,
GROUP_RADIUS,
(thing: Movable, listener: User) => this.onRoomEnter(thing, listener),
(thing: Movable, position:PositionInterface, listener:User) => this.onClientMove(thing, position, listener),
(thing: Movable, listener:User) => this.onClientLeave(thing, listener)*/
);
if (!world.anonymous) {
const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug)
world.tags = data.tags
world.policyType = Number(data.policy_type)
world = new PusherRoom(roomId, this);
if (!world.public) {
await this.updateRoomWithAdminData(world);
}
this.Worlds.set(roomId, world);
this.rooms.set(roomId, world);
}
return Promise.resolve(world)
}
/* private joinRoom(client : ExSocketInterface, position: PointInterface): PusherRoom {
const roomId = client.roomId;
client.position = position;
const world = this.Worlds.get(roomId)
if(world === undefined){
throw new Error('Could not find room for ID: '+client.roomId)
}
// Dispatch groups position to newly connected user
world.getGroups().forEach((group: Group) => {
this.emitCreateUpdateGroupEvent(client, group);
});
//join world
world.join(client, client.position);
clientEventsEmitter.emitClientJoin(client.userUuid, client.roomId);
console.log(new Date().toISOString() + ' A user joined (', this.sockets.size, ' connected users)');
return world;
public async updateRoomWithAdminData(world: PusherRoom): Promise<void> {
const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug)
world.tags = data.tags;
world.policyType = Number(data.policy_type);
}
private onClientMove(thing: Movable, position:PositionInterface, listener:User): void {
const clientListener = this.searchClientByIdOrFail(listener.id);
if (thing instanceof User) {
const clientUser = this.searchClientByIdOrFail(thing.id);
const userMovedMessage = new UserMovedMessage();
userMovedMessage.setUserid(clientUser.userId);
userMovedMessage.setPosition(ProtobufUtils.toPositionMessage(clientUser.position));
const subMessage = new SubMessage();
subMessage.setUsermovedmessage(userMovedMessage);
clientListener.emitInBatch(subMessage);
//console.log("Sending USER_MOVED event");
} else if (thing instanceof Group) {
this.emitCreateUpdateGroupEvent(clientListener, thing);
} else {
console.error('Unexpected type for Movable.');
}
}
private onClientLeave(thing: Movable, listener:User) {
const clientListener = this.searchClientByIdOrFail(listener.id);
if (thing instanceof User) {
const clientUser = this.searchClientByIdOrFail(thing.id);
this.emitUserLeftEvent(clientListener, clientUser.userId);
} else if (thing instanceof Group) {
this.emitDeleteGroupEvent(clientListener, thing.getId());
} else {
console.error('Unexpected type for Movable.');
}
}*/
emitPlayGlobalMessage(client: ExSocketInterface, playglobalmessage: PlayGlobalMessage) {
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setPlayglobalmessage(playglobalmessage);
@ -486,13 +371,9 @@ export class SocketManager implements ZoneEventListener {
}
public getWorlds(): Map<string, PusherRoom> {
return this.Worlds;
return this.rooms;
}
/**
*
* @param token
*/
searchClientByUuid(uuid: string): ExSocketInterface | null {
for(const socket of this.sockets.values()){
if(socket.userUuid === uuid){
@ -504,85 +385,92 @@ export class SocketManager implements ZoneEventListener {
public handleQueryJitsiJwtMessage(client: ExSocketInterface, queryJitsiJwtMessage: QueryJitsiJwtMessage) {
const room = queryJitsiJwtMessage.getJitsiroom();
const tag = queryJitsiJwtMessage.getTag(); // FIXME: this is not secure. We should load the JSON for the current room and check rights associated to room instead.
try {
const room = queryJitsiJwtMessage.getJitsiroom();
const tag = queryJitsiJwtMessage.getTag(); // FIXME: this is not secure. We should load the JSON for the current room and check rights associated to room instead.
if (SECRET_JITSI_KEY === '') {
throw new Error('You must set the SECRET_JITSI_KEY key to the secret to generate JWT tokens for Jitsi.');
if (SECRET_JITSI_KEY === '') {
throw new Error('You must set the SECRET_JITSI_KEY key to the secret to generate JWT tokens for Jitsi.');
}
// Let's see if the current client has
const isAdmin = client.tags.includes(tag);
const jwt = Jwt.sign({
"aud": "jitsi",
"iss": JITSI_ISS,
"sub": JITSI_URL,
"room": room,
"moderator": isAdmin
}, SECRET_JITSI_KEY, {
expiresIn: '1d',
algorithm: "HS256",
header:
{
"alg": "HS256",
"typ": "JWT"
}
});
const sendJitsiJwtMessage = new SendJitsiJwtMessage();
sendJitsiJwtMessage.setJitsiroom(room);
sendJitsiJwtMessage.setJwt(jwt);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendjitsijwtmessage(sendJitsiJwtMessage);
client.send(serverToClientMessage.serializeBinary().buffer, true);
} catch (e) {
console.error('An error occured while generating the Jitsi JWT token: ', e);
}
// Let's see if the current client has
const isAdmin = client.tags.includes(tag);
const jwt = Jwt.sign({
"aud": "jitsi",
"iss": JITSI_ISS,
"sub": JITSI_URL,
"room": room,
"moderator": isAdmin
}, SECRET_JITSI_KEY, {
expiresIn: '1d',
algorithm: "HS256",
header:
{
"alg": "HS256",
"typ": "JWT"
}
});
const sendJitsiJwtMessage = new SendJitsiJwtMessage();
sendJitsiJwtMessage.setJitsiroom(room);
sendJitsiJwtMessage.setJwt(jwt);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendjitsijwtmessage(sendJitsiJwtMessage);
client.send(serverToClientMessage.serializeBinary().buffer, true);
}
public async emitSendUserMessage(userUuid: string, message: string, roomId: string): Promise<void> {
public async emitSendUserMessage(userUuid: string, message: string, type: string, roomId: string) {
/*const client = this.searchClientByUuid(userUuid);
if(client) {
const adminMessage = new SendUserMessage();
adminMessage.setMessage(message);
adminMessage.setType(type);
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setSendusermessage(adminMessage);
client.backConnection.write(pusherToBackMessage);
return;
}*/
const backConnection = await apiClientRepository.getClient(roomId);
const adminMessage = new AdminMessage();
adminMessage.setRecipientuuid(userUuid);
adminMessage.setMessage(message);
adminMessage.setRoomid(roomId);
backConnection.sendAdminMessage(adminMessage, (error) => {
const backAdminMessage = new AdminMessage();
backAdminMessage.setMessage(message);
backAdminMessage.setRoomid(roomId);
backAdminMessage.setRecipientuuid(userUuid);
backAdminMessage.setType(type);
backConnection.sendAdminMessage(backAdminMessage, (error) => {
if (error !== null) {
console.error('Error while sending admin message', error);
}
});
/*
const socket = this.searchClientByUuid(messageToSend.userUuid);
if(!socket){
throw 'socket was not found';
}
const sendUserMessage = new SendUserMessage();
sendUserMessage.setMessage(messageToSend.message);
sendUserMessage.setType(messageToSend.type);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendusermessage(sendUserMessage);
if (!socket.disconnecting) {
socket.send(serverToClientMessage.serializeBinary().buffer, true);
}
return socket;*/
}
public async emitBan(userUuid: string, message: string, roomId: string): Promise<void> {
public async emitBan(userUuid: string, message: string, type: string, roomId: string) {
/*const client = this.searchClientByUuid(userUuid);
if(client) {
const banUserMessage = new BanUserMessage();
banUserMessage.setMessage(message);
banUserMessage.setType(type);
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setBanusermessage(banUserMessage);
client.backConnection.write(pusherToBackMessage);
return;
}*/
const backConnection = await apiClientRepository.getClient(roomId);
const banMessage = new BanMessage();
banMessage.setRecipientuuid(userUuid);
banMessage.setMessage(message);
banMessage.setRoomid(roomId);
banMessage.setRecipientuuid(userUuid);
banMessage.setType(type);
backConnection.ban(banMessage, (error) => {
if (error !== null) {
console.error('Error while sending ban message', error);
console.error('Error while sending admin message', error);
}
});
}
@ -658,6 +546,23 @@ export class SocketManager implements ZoneEventListener {
emitInBatch(listener, subMessage);
}
public emitWorldFullMessage(client: WebSocket) {
const errorMessage = new WorldFullMessage();
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setWorldfullmessage(errorMessage);
client.send(serverToClientMessage.serializeBinary().buffer, true);
}
private refreshRoomData(roomId: string, versionNumber: number): void {
const room = this.rooms.get(roomId);
//this function is run for every users connected to the room, so we need to make sure the room wasn't already refreshed.
if (!room || !room.needsUpdate(versionNumber)) return;
this.updateRoomWithAdminData(room);
}
}
export const socketManager = new SocketManager();

View file

@ -2828,9 +2828,9 @@ xtend@^4.0.0:
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
y18n@^3.2.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
version "3.2.2"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696"
integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==
yallist@^3.0.0, yallist@^3.0.3:
version "3.1.1"