Fixing Uploader build
This commit is contained in:
parent
ea3aa3d128
commit
10e1400fc0
4
.github/workflows/build-and-deploy.yml
vendored
4
.github/workflows/build-and-deploy.yml
vendored
@ -153,6 +153,10 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- build-front
|
- build-front
|
||||||
- build-back
|
- build-back
|
||||||
|
- build-pusher
|
||||||
|
- build-maps
|
||||||
|
- build-uploader
|
||||||
|
- build-website
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
@ -44,11 +44,13 @@
|
|||||||
"iterall": "^1.3.0",
|
"iterall": "^1.3.0",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"multer": "^1.4.2",
|
"multer": "^1.4.2",
|
||||||
|
"mkdirp": "^1.0.4",
|
||||||
"prom-client": "^12.0.0",
|
"prom-client": "^12.0.0",
|
||||||
"query-string": "^6.13.3",
|
"query-string": "^6.13.3",
|
||||||
"ts-node-dev": "^1.0.0-pre.44",
|
"ts-node-dev": "^1.0.0-pre.44",
|
||||||
"typescript": "^3.8.3",
|
"typescript": "^3.8.3",
|
||||||
"uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0"
|
"uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0",
|
||||||
|
"uuidv4": "^6.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/busboy": "^0.2.3",
|
"@types/busboy": "^0.2.3",
|
||||||
@ -59,6 +61,8 @@
|
|||||||
"@types/jsonwebtoken": "^8.3.8",
|
"@types/jsonwebtoken": "^8.3.8",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
||||||
"@typescript-eslint/parser": "^2.26.0",
|
"@typescript-eslint/parser": "^2.26.0",
|
||||||
|
"@types/mkdirp": "^1.0.1",
|
||||||
|
"@types/uuidv4": "^5.0.0",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
"jasmine": "^3.5.0"
|
"jasmine": "^3.5.0"
|
||||||
}
|
}
|
||||||
|
@ -1,115 +0,0 @@
|
|||||||
import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable";
|
|
||||||
import Axios from "axios";
|
|
||||||
import {v4} from "uuid";
|
|
||||||
|
|
||||||
export interface AdminApiData {
|
|
||||||
organizationSlug: string
|
|
||||||
worldSlug: string
|
|
||||||
roomSlug: string
|
|
||||||
mapUrlStart: string
|
|
||||||
tags: string[]
|
|
||||||
policy_type: number
|
|
||||||
userUuid: string
|
|
||||||
messages?: unknown[],
|
|
||||||
textures: CharacterTexture[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CharacterTexture {
|
|
||||||
id: number,
|
|
||||||
level: number,
|
|
||||||
url: string,
|
|
||||||
rights: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FetchMemberDataByUuidResponse {
|
|
||||||
uuid: string;
|
|
||||||
tags: string[];
|
|
||||||
textures: CharacterTexture[];
|
|
||||||
messages: unknown[];
|
|
||||||
}
|
|
||||||
|
|
||||||
class AdminApi {
|
|
||||||
|
|
||||||
async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise<AdminApiData> {
|
|
||||||
if (!ADMIN_API_URL) {
|
|
||||||
return Promise.reject('No admin backoffice set!');
|
|
||||||
}
|
|
||||||
|
|
||||||
const params: { organizationSlug: string, worldSlug: string, roomSlug?: string } = {
|
|
||||||
organizationSlug,
|
|
||||||
worldSlug
|
|
||||||
};
|
|
||||||
|
|
||||||
if (roomSlug) {
|
|
||||||
params.roomSlug = roomSlug;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await Axios.get(ADMIN_API_URL + '/api/map',
|
|
||||||
{
|
|
||||||
headers: {"Authorization": `${ADMIN_API_TOKEN}`},
|
|
||||||
params
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return res.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchMemberDataByUuid(uuid: 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchMemberDataByToken(organizationMemberToken: string): Promise<AdminApiData> {
|
|
||||||
if (!ADMIN_API_URL) {
|
|
||||||
return Promise.reject('No admin backoffice set!');
|
|
||||||
}
|
|
||||||
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
|
||||||
const res = await Axios.get(ADMIN_API_URL+'/api/login-url/'+organizationMemberToken,
|
|
||||||
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
|
|
||||||
)
|
|
||||||
return res.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchCheckUserByToken(organizationMemberToken: string): Promise<AdminApiData> {
|
|
||||||
if (!ADMIN_API_URL) {
|
|
||||||
return Promise.reject('No admin backoffice set!');
|
|
||||||
}
|
|
||||||
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
|
||||||
const res = await Axios.get(ADMIN_API_URL+'/api/check-user/'+organizationMemberToken,
|
|
||||||
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
|
|
||||||
)
|
|
||||||
return res.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string) {
|
|
||||||
return Axios.post(`${ADMIN_API_URL}/api/report`, {
|
|
||||||
reportedUserUuid,
|
|
||||||
reportedUserComment,
|
|
||||||
reporterUserUuid,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headers: {"Authorization": `${ADMIN_API_TOKEN}`}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const adminApi = new AdminApi();
|
|
@ -1,22 +0,0 @@
|
|||||||
/**
|
|
||||||
* A class to get connections to the correct "api" server given a room name.
|
|
||||||
*/
|
|
||||||
import {RoomManagerClient} from "../Messages/generated/messages_grpc_pb";
|
|
||||||
import grpc from 'grpc';
|
|
||||||
import {API_URL} from "../Enum/EnvironmentVariable";
|
|
||||||
|
|
||||||
|
|
||||||
class ApiClientRepository {
|
|
||||||
private roomManagerClient: RoomManagerClient|null = null;
|
|
||||||
|
|
||||||
public async getClient(roomId: string): Promise<RoomManagerClient> {
|
|
||||||
if (this.roomManagerClient === null) {
|
|
||||||
this.roomManagerClient = new RoomManagerClient(API_URL, grpc.credentials.createInsecure());
|
|
||||||
}
|
|
||||||
return Promise.resolve(this.roomManagerClient);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const apiClientRepository = new ApiClientRepository();
|
|
||||||
|
|
||||||
export { apiClientRepository };
|
|
@ -1,3 +0,0 @@
|
|||||||
export const arrayIntersect = (array1: string[], array2: string[]) : boolean => {
|
|
||||||
return array1.filter(value => array2.includes(value)).length > 0;
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
const EventEmitter = require('events');
|
|
||||||
|
|
||||||
const clientJoinEvent = 'clientJoin';
|
|
||||||
const clientLeaveEvent = 'clientLeave';
|
|
||||||
|
|
||||||
class ClientEventsEmitter extends EventEmitter {
|
|
||||||
emitClientJoin(clientUUid: string, roomId: string): void {
|
|
||||||
this.emit(clientJoinEvent, clientUUid, roomId);
|
|
||||||
}
|
|
||||||
|
|
||||||
emitClientLeave(clientUUid: string, roomId: string): void {
|
|
||||||
this.emit(clientLeaveEvent, clientUUid, roomId);
|
|
||||||
}
|
|
||||||
|
|
||||||
registerToClientJoin(callback: (clientUUid: string, roomId: string) => void): void {
|
|
||||||
this.on(clientJoinEvent, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
registerToClientLeave(callback: (clientUUid: string, roomId: string) => void): void {
|
|
||||||
this.on(clientLeaveEvent, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
unregisterFromClientJoin(callback: (clientUUid: string, roomId: string) => void): void {
|
|
||||||
this.removeListener(clientJoinEvent, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
unregisterFromClientLeave(callback: (clientUUid: string, roomId: string) => void): void {
|
|
||||||
this.removeListener(clientLeaveEvent, callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const clientEventsEmitter = new ClientEventsEmitter();
|
|
@ -1,55 +0,0 @@
|
|||||||
import {CPU_OVERHEAT_THRESHOLD} from "../Enum/EnvironmentVariable";
|
|
||||||
|
|
||||||
function secNSec2ms(secNSec: Array<number>|number) {
|
|
||||||
if (Array.isArray(secNSec)) {
|
|
||||||
return secNSec[0] * 1000 + secNSec[1] / 1000000;
|
|
||||||
}
|
|
||||||
return secNSec / 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
class CpuTracker {
|
|
||||||
private cpuPercent: number = 0;
|
|
||||||
private overHeating: boolean = false;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
let time = process.hrtime.bigint()
|
|
||||||
let usage = process.cpuUsage()
|
|
||||||
setInterval(() => {
|
|
||||||
const elapTime = process.hrtime.bigint();
|
|
||||||
const elapUsage = process.cpuUsage(usage)
|
|
||||||
usage = process.cpuUsage()
|
|
||||||
|
|
||||||
const elapTimeMS = elapTime - time;
|
|
||||||
const elapUserMS = secNSec2ms(elapUsage.user)
|
|
||||||
const elapSystMS = secNSec2ms(elapUsage.system)
|
|
||||||
this.cpuPercent = Math.round(100 * (elapUserMS + elapSystMS) / Number(elapTimeMS) * 1000000)
|
|
||||||
|
|
||||||
time = elapTime;
|
|
||||||
|
|
||||||
if (!this.overHeating && this.cpuPercent > CPU_OVERHEAT_THRESHOLD) {
|
|
||||||
this.overHeating = true;
|
|
||||||
console.warn('CPU high threshold alert. Going in "overheat" mode');
|
|
||||||
} else if (this.overHeating && this.cpuPercent <= CPU_OVERHEAT_THRESHOLD) {
|
|
||||||
this.overHeating = false;
|
|
||||||
console.log('CPU is back to normal. Canceling "overheat" mode');
|
|
||||||
}
|
|
||||||
|
|
||||||
/*console.log('elapsed time ms: ', elapTimeMS)
|
|
||||||
console.log('elapsed user ms: ', elapUserMS)
|
|
||||||
console.log('elapsed system ms:', elapSystMS)
|
|
||||||
console.log('cpu percent: ', this.cpuPercent)*/
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCpuPercent(): number {
|
|
||||||
return this.cpuPercent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isOverHeating(): boolean {
|
|
||||||
return this.overHeating;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cpuTracker = new CpuTracker();
|
|
||||||
|
|
||||||
export { cpuTracker };
|
|
@ -1,54 +0,0 @@
|
|||||||
import {Counter, Gauge} from "prom-client";
|
|
||||||
|
|
||||||
//this class should manage all the custom metrics used by prometheus
|
|
||||||
class GaugeManager {
|
|
||||||
private nbClientsGauge: Gauge<string>;
|
|
||||||
private nbClientsPerRoomGauge: Gauge<string>;
|
|
||||||
private nbGroupsPerRoomGauge: Gauge<string>;
|
|
||||||
private nbGroupsPerRoomCounter: Counter<string>;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.nbClientsGauge = new Gauge({
|
|
||||||
name: 'workadventure_nb_sockets',
|
|
||||||
help: 'Number of connected sockets',
|
|
||||||
labelNames: [ ]
|
|
||||||
});
|
|
||||||
this.nbClientsPerRoomGauge = new Gauge({
|
|
||||||
name: 'workadventure_nb_clients_per_room',
|
|
||||||
help: 'Number of clients per room',
|
|
||||||
labelNames: [ 'room' ]
|
|
||||||
});
|
|
||||||
|
|
||||||
this.nbGroupsPerRoomCounter = new Counter({
|
|
||||||
name: 'workadventure_counter_groups_per_room',
|
|
||||||
help: 'Counter of groups per room',
|
|
||||||
labelNames: [ 'room' ]
|
|
||||||
});
|
|
||||||
this.nbGroupsPerRoomGauge = new Gauge({
|
|
||||||
name: 'workadventure_nb_groups_per_room',
|
|
||||||
help: 'Number of groups per room',
|
|
||||||
labelNames: [ 'room' ]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
incNbClientPerRoomGauge(roomId: string): void {
|
|
||||||
this.nbClientsGauge.inc();
|
|
||||||
this.nbClientsPerRoomGauge.inc({ room: roomId });
|
|
||||||
}
|
|
||||||
|
|
||||||
decNbClientPerRoomGauge(roomId: string): void {
|
|
||||||
this.nbClientsGauge.dec();
|
|
||||||
this.nbClientsPerRoomGauge.dec({ room: roomId });
|
|
||||||
}
|
|
||||||
|
|
||||||
incNbGroupsPerRoomGauge(roomId: string): void {
|
|
||||||
this.nbGroupsPerRoomCounter.inc({ room: roomId })
|
|
||||||
this.nbGroupsPerRoomGauge.inc({ room: roomId })
|
|
||||||
}
|
|
||||||
|
|
||||||
decNbGroupsPerRoomGauge(roomId: string): void {
|
|
||||||
this.nbGroupsPerRoomGauge.dec({ room: roomId })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const gaugeManager = new GaugeManager();
|
|
@ -1,35 +0,0 @@
|
|||||||
import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
|
|
||||||
import {BatchMessage, ErrorMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb";
|
|
||||||
|
|
||||||
export function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void {
|
|
||||||
socket.batchedMessages.addPayload(payload);
|
|
||||||
|
|
||||||
if (socket.batchTimeout === null) {
|
|
||||||
socket.batchTimeout = setTimeout(() => {
|
|
||||||
if (socket.disconnecting) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const serverToClientMessage = new ServerToClientMessage();
|
|
||||||
serverToClientMessage.setBatchmessage(socket.batchedMessages);
|
|
||||||
|
|
||||||
socket.send(serverToClientMessage.serializeBinary().buffer, true);
|
|
||||||
socket.batchedMessages = new BatchMessage();
|
|
||||||
socket.batchTimeout = null;
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function emitError(Client: ExSocketInterface, message: string): void {
|
|
||||||
const errorMessage = new ErrorMessage();
|
|
||||||
errorMessage.setMessage(message);
|
|
||||||
|
|
||||||
const serverToClientMessage = new ServerToClientMessage();
|
|
||||||
serverToClientMessage.setErrormessage(errorMessage);
|
|
||||||
|
|
||||||
if (!Client.disconnecting) {
|
|
||||||
Client.send(serverToClientMessage.serializeBinary().buffer, true);
|
|
||||||
}
|
|
||||||
console.warn(message);
|
|
||||||
}
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
|||||||
import {ADMIN_API_URL, ALLOW_ARTILLERY, SECRET_KEY} from "../Enum/EnvironmentVariable";
|
|
||||||
import {uuid} from "uuidv4";
|
|
||||||
import Jwt from "jsonwebtoken";
|
|
||||||
import {TokenInterface} from "../Controller/AuthenticateController";
|
|
||||||
import {adminApi, AdminApiData} from "../Services/AdminApi";
|
|
||||||
|
|
||||||
class JWTTokenManager {
|
|
||||||
|
|
||||||
public createJWTToken(userUuid: string) {
|
|
||||||
return Jwt.sign({userUuid: userUuid}, SECRET_KEY, {expiresIn: '200d'}); //todo: add a mechanic to refresh or recreate token
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getUserUuidFromToken(token: unknown): Promise<string> {
|
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
throw new Error('An authentication error happened, a user tried to connect without a token.');
|
|
||||||
}
|
|
||||||
if (typeof(token) !== "string") {
|
|
||||||
throw new Error('Token is expected to be a string');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if(token === 'test') {
|
|
||||||
if (ALLOW_ARTILLERY) {
|
|
||||||
return uuid();
|
|
||||||
} else {
|
|
||||||
throw new Error("In order to perform a load-testing test on this environment, you must set the ALLOW_ARTILLERY environment variable to 'true'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise<string>((resolve, reject) => {
|
|
||||||
Jwt.verify(token, SECRET_KEY, {},(err, tokenDecoded) => {
|
|
||||||
const tokenInterface = tokenDecoded as TokenInterface;
|
|
||||||
if (err) {
|
|
||||||
console.error('An authentication error happened, invalid JsonWebToken.', err);
|
|
||||||
reject(new Error('An authentication error happened, invalid JsonWebToken. ' + err.message));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (tokenDecoded === undefined) {
|
|
||||||
console.error('Empty token found.');
|
|
||||||
reject(new Error('Empty token found.'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//verify token
|
|
||||||
if (!this.isValidToken(tokenInterface)) {
|
|
||||||
reject(new Error('Authentication error, invalid token structure.'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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){
|
|
||||||
resolve(tokenInterface.userUuid);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
resolve(tokenInterface.userUuid);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private isValidToken(token: object): token is TokenInterface {
|
|
||||||
return !(typeof((token as TokenInterface).userUuid) !== 'string');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export const jwtTokenManager = new JWTTokenManager();
|
|
@ -1,747 +0,0 @@
|
|||||||
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
|
|
||||||
} from "../Messages/generated/messages_pb";
|
|
||||||
import {PointInterface} from "../Model/Websocket/PointInterface";
|
|
||||||
import {User} from "../Model/User";
|
|
||||||
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
|
||||||
import {Group} from "../Model/Group";
|
|
||||||
import {cpuTracker} from "./CpuTracker";
|
|
||||||
import {GROUP_RADIUS, JITSI_ISS, MINIMUM_DISTANCE, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
|
|
||||||
import {Movable} from "../Model/Movable";
|
|
||||||
import {PositionInterface} from "../Model/PositionInterface";
|
|
||||||
import {adminApi, CharacterTexture} from "./AdminApi";
|
|
||||||
import Direction = PositionMessage.Direction;
|
|
||||||
import {emitError, 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";
|
|
||||||
|
|
||||||
const debug = Debug('socket');
|
|
||||||
|
|
||||||
interface AdminSocketRoomsList {
|
|
||||||
[index: string]: number;
|
|
||||||
}
|
|
||||||
interface AdminSocketUsersList {
|
|
||||||
[index: string]: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AdminSocketData {
|
|
||||||
rooms: AdminSocketRoomsList,
|
|
||||||
users: AdminSocketUsersList,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SocketManager implements ZoneEventListener {
|
|
||||||
private Worlds: Map<string, PusherRoom> = new Map<string, PusherRoom>();
|
|
||||||
private sockets: Map<number, ExSocketInterface> = new Map<number, ExSocketInterface>();
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
clientEventsEmitter.registerToClientJoin((clientUUid: string, roomId: string) => {
|
|
||||||
gaugeManager.incNbClientPerRoomGauge(roomId);
|
|
||||||
});
|
|
||||||
clientEventsEmitter.registerToClientLeave((clientUUid: string, roomId: string) => {
|
|
||||||
gaugeManager.decNbClientPerRoomGauge(roomId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getAdminSocketDataFor(roomId:string): AdminSocketData {
|
|
||||||
throw new Error('Not reimplemented yet');
|
|
||||||
/*const data:AdminSocketData = {
|
|
||||||
rooms: {},
|
|
||||||
users: {},
|
|
||||||
}
|
|
||||||
const room = this.Worlds.get(roomId);
|
|
||||||
if (room === undefined) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
const users = room.getUsers();
|
|
||||||
data.rooms[roomId] = users.size;
|
|
||||||
users.forEach(user => {
|
|
||||||
data.users[user.uuid] = true
|
|
||||||
})
|
|
||||||
return data;*/
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleJoinRoom(client: ExSocketInterface): Promise<void> {
|
|
||||||
const position = client.position;
|
|
||||||
const viewport = client.viewport;
|
|
||||||
try {
|
|
||||||
|
|
||||||
const joinRoomMessage = new JoinRoomMessage();
|
|
||||||
joinRoomMessage.setUseruuid(client.userUuid);
|
|
||||||
joinRoomMessage.setRoomid(client.roomId);
|
|
||||||
joinRoomMessage.setName(client.name);
|
|
||||||
joinRoomMessage.setPositionmessage(ProtobufUtils.toPositionMessage(client.position));
|
|
||||||
for (const characterLayer of client.characterLayers) {
|
|
||||||
const characterLayerMessage = new CharacterLayerMessage();
|
|
||||||
characterLayerMessage.setName(characterLayer.name);
|
|
||||||
if (characterLayer.url !== undefined) {
|
|
||||||
characterLayerMessage.setUrl(characterLayer.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
joinRoomMessage.addCharacterlayer(characterLayerMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
console.log('Calling joinRoom')
|
|
||||||
const apiClient = await apiClientRepository.getClient(client.roomId);
|
|
||||||
const streamToPusher = apiClient.joinRoom();
|
|
||||||
|
|
||||||
client.backConnection = streamToPusher;
|
|
||||||
|
|
||||||
streamToPusher.on('data', (message: ServerToClientMessage) => {
|
|
||||||
if (message.hasRoomjoinedmessage()) {
|
|
||||||
client.userId = (message.getRoomjoinedmessage() as RoomJoinedMessage).getCurrentuserid();
|
|
||||||
// TODO: do we need this.sockets anymore?
|
|
||||||
this.sockets.set(client.userId, client);
|
|
||||||
|
|
||||||
// If this is the first message sent, send back the viewport.
|
|
||||||
this.handleViewport(client, viewport);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let's pass data over from the back to the client.
|
|
||||||
if (!client.disconnecting) {
|
|
||||||
client.send(message.serializeBinary().buffer, true);
|
|
||||||
}
|
|
||||||
}).on('end', () => {
|
|
||||||
console.warn('Connection lost to back server');
|
|
||||||
// Let's close the front connection if the back connection is closed. This way, we can retry connecting from the start.
|
|
||||||
if (!client.disconnecting) {
|
|
||||||
this.closeWebsocketConnection(client, 1011, 'Connection lost to back server');
|
|
||||||
}
|
|
||||||
console.log('A user left');
|
|
||||||
}).on('error', (err: Error) => {
|
|
||||||
console.error('Error in connection to back server:', err);
|
|
||||||
if (!client.disconnecting) {
|
|
||||||
this.closeWebsocketConnection(client, 1011, 'Error while connecting to back server');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const pusherToBackMessage = new PusherToBackMessage();
|
|
||||||
pusherToBackMessage.setJoinroommessage(joinRoomMessage);
|
|
||||||
streamToPusher.write(pusherToBackMessage);
|
|
||||||
|
|
||||||
// TODO: analyze viewport, subscribe to correct handler
|
|
||||||
|
|
||||||
//join new previous room
|
|
||||||
//const gameRoom = this.joinRoom(client, position);
|
|
||||||
|
|
||||||
//const things = gameRoom.setViewport(client, viewport);
|
|
||||||
|
|
||||||
/*const roomJoinedMessage = new RoomJoinedMessage();
|
|
||||||
|
|
||||||
for (const thing of things) {
|
|
||||||
if (thing instanceof User) {
|
|
||||||
const player: ExSocketInterface|undefined = this.sockets.get(thing.id);
|
|
||||||
if (player === undefined) {
|
|
||||||
console.warn('Something went wrong. The World contains a user "'+thing.id+"' but this user does not exist in the sockets list!");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userJoinedMessage = new UserJoinedMessage();
|
|
||||||
userJoinedMessage.setUserid(thing.id);
|
|
||||||
userJoinedMessage.setName(player.name);
|
|
||||||
userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(player.characterLayers));
|
|
||||||
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(player.position));
|
|
||||||
|
|
||||||
roomJoinedMessage.addUser(userJoinedMessage);
|
|
||||||
roomJoinedMessage.setTagList(client.tags);
|
|
||||||
} else if (thing instanceof Group) {
|
|
||||||
const groupUpdateMessage = new GroupUpdateMessage();
|
|
||||||
groupUpdateMessage.setGroupid(thing.getId());
|
|
||||||
groupUpdateMessage.setPosition(ProtobufUtils.toPointMessage(thing.getPosition()));
|
|
||||||
|
|
||||||
roomJoinedMessage.addGroup(groupUpdateMessage);
|
|
||||||
} else {
|
|
||||||
console.error("Unexpected type for Movable returned by setViewport");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [itemId, item] of gameRoom.getItemsState().entries()) {
|
|
||||||
const itemStateMessage = new ItemStateMessage();
|
|
||||||
itemStateMessage.setItemid(itemId);
|
|
||||||
itemStateMessage.setStatejson(JSON.stringify(item));
|
|
||||||
|
|
||||||
roomJoinedMessage.addItem(itemStateMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
roomJoinedMessage.setCurrentuserid(client.userId);
|
|
||||||
|
|
||||||
const serverToClientMessage = new ServerToClientMessage();
|
|
||||||
serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage);
|
|
||||||
|
|
||||||
if (!client.disconnecting) {
|
|
||||||
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
|
||||||
}*/
|
|
||||||
} catch (e) {
|
|
||||||
console.error('An error occurred on "join_room" event');
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private closeWebsocketConnection(client: ExSocketInterface, code: number, reason: string) {
|
|
||||||
client.disconnecting = true;
|
|
||||||
//this.leaveRoom(client);
|
|
||||||
//client.close();
|
|
||||||
client.end(code, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleViewport(client: ExSocketInterface, viewport: ViewportMessage.AsObject) {
|
|
||||||
try {
|
|
||||||
client.viewport = viewport;
|
|
||||||
|
|
||||||
const world = this.Worlds.get(client.roomId);
|
|
||||||
if (!world) {
|
|
||||||
console.error("In SET_VIEWPORT, could not find world with id '", client.roomId, "'");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
world.setViewport(client, client.viewport);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('An error occurred on "SET_VIEWPORT" event');
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleUserMovesMessage(client: ExSocketInterface, userMovesMessage: UserMovesMessage) {
|
|
||||||
const pusherToBackMessage = new PusherToBackMessage();
|
|
||||||
pusherToBackMessage.setUsermovesmessage(userMovesMessage);
|
|
||||||
|
|
||||||
client.backConnection.write(pusherToBackMessage);
|
|
||||||
|
|
||||||
const viewport = userMovesMessage.getViewport();
|
|
||||||
if (viewport === undefined) {
|
|
||||||
throw new Error('Missing viewport in UserMovesMessage');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now, we need to listen to the correct viewport.
|
|
||||||
this.handleViewport(client, viewport.toObject())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Useless now, will be useful again if we allow editing details in game
|
|
||||||
handleSetPlayerDetails(client: ExSocketInterface, playerDetailsMessage: SetPlayerDetailsMessage) {
|
|
||||||
const pusherToBackMessage = new PusherToBackMessage();
|
|
||||||
pusherToBackMessage.setSetplayerdetailsmessage(playerDetailsMessage);
|
|
||||||
|
|
||||||
client.backConnection.write(pusherToBackMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSilentMessage(client: ExSocketInterface, silentMessage: SilentMessage) {
|
|
||||||
const pusherToBackMessage = new PusherToBackMessage();
|
|
||||||
pusherToBackMessage.setSilentmessage(silentMessage);
|
|
||||||
|
|
||||||
client.backConnection.write(pusherToBackMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleItemEvent(client: ExSocketInterface, itemEventMessage: ItemEventMessage) {
|
|
||||||
const pusherToBackMessage = new PusherToBackMessage();
|
|
||||||
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) {
|
|
||||||
try {
|
|
||||||
const reportedSocket = this.sockets.get(reportPlayerMessage.getReporteduserid());
|
|
||||||
if (!reportedSocket) {
|
|
||||||
throw 'reported socket user not found';
|
|
||||||
}
|
|
||||||
//TODO report user on admin application
|
|
||||||
await adminApi.reportPlayer(reportedSocket.userUuid, reportPlayerMessage.getReportcomment(), client.userUuid)
|
|
||||||
} catch (e) {
|
|
||||||
console.error('An error occurred on "handleReportMessage"');
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emitVideo(socket: ExSocketInterface, data: WebRtcSignalToServerMessage): void {
|
|
||||||
const pusherToBackMessage = new PusherToBackMessage();
|
|
||||||
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 {
|
|
||||||
const pusherToBackMessage = new PusherToBackMessage();
|
|
||||||
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 {
|
|
||||||
const client: ExSocketInterface|undefined = this.sockets.get(userId);
|
|
||||||
if (client === undefined) {
|
|
||||||
throw new Error("Could not find user with id " + userId);
|
|
||||||
}
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
leaveRoom(socket : ExSocketInterface) {
|
|
||||||
// leave previous room and world
|
|
||||||
try {
|
|
||||||
if (socket.roomId) {
|
|
||||||
try {
|
|
||||||
//user leaves room
|
|
||||||
const room: PusherRoom | undefined = this.Worlds.get(socket.roomId);
|
|
||||||
if (room) {
|
|
||||||
debug('Leaving room %s.', socket.roomId);
|
|
||||||
room.leave(socket);
|
|
||||||
if (room.isEmpty()) {
|
|
||||||
this.Worlds.delete(socket.roomId);
|
|
||||||
debug('Room %s is empty. Deleting.', socket.roomId);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error('Could not find the GameRoom the user is leaving!');
|
|
||||||
}
|
|
||||||
//user leave previous room
|
|
||||||
//Client.leave(Client.roomId);
|
|
||||||
} finally {
|
|
||||||
//delete Client.roomId;
|
|
||||||
this.sockets.delete(socket.userId);
|
|
||||||
clientEventsEmitter.emitClientLeave(socket.userUuid, socket.roomId);
|
|
||||||
console.log('A user left (', this.sockets.size, ' connected users)');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (socket.backConnection) {
|
|
||||||
socket.backConnection.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getOrCreateRoom(roomId: string): Promise<PusherRoom> {
|
|
||||||
//check and create new world for a room
|
|
||||||
let world = this.Worlds.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)
|
|
||||||
}
|
|
||||||
this.Worlds.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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.');
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
private emitCreateUpdateGroupEvent(client: ExSocketInterface, group: Group): void {
|
|
||||||
const position = group.getPosition();
|
|
||||||
const pointMessage = new PointMessage();
|
|
||||||
pointMessage.setX(Math.floor(position.x));
|
|
||||||
pointMessage.setY(Math.floor(position.y));
|
|
||||||
const groupUpdateMessage = new GroupUpdateMessage();
|
|
||||||
groupUpdateMessage.setGroupid(group.getId());
|
|
||||||
groupUpdateMessage.setPosition(pointMessage);
|
|
||||||
groupUpdateMessage.setGroupsize(group.getSize);
|
|
||||||
|
|
||||||
const subMessage = new SubMessage();
|
|
||||||
subMessage.setGroupupdatemessage(groupUpdateMessage);
|
|
||||||
|
|
||||||
emitInBatch(client, subMessage);
|
|
||||||
//socket.emit(SocketIoEvent.GROUP_CREATE_UPDATE, groupUpdateMessage.serializeBinary().buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private emitDeleteGroupEvent(client: ExSocketInterface, groupId: number): void {
|
|
||||||
const groupDeleteMessage = new GroupDeleteMessage();
|
|
||||||
groupDeleteMessage.setGroupid(groupId);
|
|
||||||
|
|
||||||
const subMessage = new SubMessage();
|
|
||||||
subMessage.setGroupdeletemessage(groupDeleteMessage);
|
|
||||||
|
|
||||||
emitInBatch(client, subMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private emitUserLeftEvent(client: ExSocketInterface, userId: number): void {
|
|
||||||
const userLeftMessage = new UserLeftMessage();
|
|
||||||
userLeftMessage.setUserid(userId);
|
|
||||||
|
|
||||||
const subMessage = new SubMessage();
|
|
||||||
subMessage.setUserleftmessage(userLeftMessage);
|
|
||||||
|
|
||||||
emitInBatch(client, subMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private joinWebRtcRoom(user: User, group: Group) {
|
|
||||||
/*const roomId: string = "webrtcroom"+group.getId();
|
|
||||||
if (user.socket.webRtcRoomId === roomId) {
|
|
||||||
return;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
for (const otherUser of group.getUsers()) {
|
|
||||||
if (user === otherUser) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let's send 2 messages: one to the user joining the group and one to the other user
|
|
||||||
const webrtcStartMessage1 = new WebRtcStartMessage();
|
|
||||||
webrtcStartMessage1.setUserid(otherUser.id);
|
|
||||||
webrtcStartMessage1.setName(otherUser.socket.name);
|
|
||||||
webrtcStartMessage1.setInitiator(true);
|
|
||||||
|
|
||||||
const serverToClientMessage1 = new ServerToClientMessage();
|
|
||||||
serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1);
|
|
||||||
|
|
||||||
if (!user.socket.disconnecting) {
|
|
||||||
user.socket.send(serverToClientMessage1.serializeBinary().buffer, true);
|
|
||||||
//console.log('Sending webrtcstart initiator to '+user.socket.userId)
|
|
||||||
}
|
|
||||||
|
|
||||||
const webrtcStartMessage2 = new WebRtcStartMessage();
|
|
||||||
webrtcStartMessage2.setUserid(user.id);
|
|
||||||
webrtcStartMessage2.setName(user.socket.name);
|
|
||||||
webrtcStartMessage2.setInitiator(false);
|
|
||||||
|
|
||||||
const serverToClientMessage2 = new ServerToClientMessage();
|
|
||||||
serverToClientMessage2.setWebrtcstartmessage(webrtcStartMessage2);
|
|
||||||
|
|
||||||
if (!otherUser.socket.disconnecting) {
|
|
||||||
otherUser.socket.send(serverToClientMessage2.serializeBinary().buffer, true);
|
|
||||||
//console.log('Sending webrtcstart to '+otherUser.socket.userId)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//disconnect user
|
|
||||||
private disConnectedUser(user: User, group: Group) {
|
|
||||||
// Most of the time, sending a disconnect event to one of the players is enough (the player will close the connection
|
|
||||||
// which will be shut for the other player).
|
|
||||||
// However! In the rare case where the WebRTC connection is not yet established, if we close the connection on one of the player,
|
|
||||||
// the other player will try connecting until a timeout happens (during this time, the connection icon will be displayed for nothing).
|
|
||||||
// So we also send the disconnect event to the other player.
|
|
||||||
for (const otherUser of group.getUsers()) {
|
|
||||||
if (user === otherUser) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const webrtcDisconnectMessage1 = new WebRtcDisconnectMessage();
|
|
||||||
webrtcDisconnectMessage1.setUserid(user.id);
|
|
||||||
|
|
||||||
const serverToClientMessage1 = new ServerToClientMessage();
|
|
||||||
serverToClientMessage1.setWebrtcdisconnectmessage(webrtcDisconnectMessage1);
|
|
||||||
|
|
||||||
if (!otherUser.socket.disconnecting) {
|
|
||||||
otherUser.socket.send(serverToClientMessage1.serializeBinary().buffer, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const webrtcDisconnectMessage2 = new WebRtcDisconnectMessage();
|
|
||||||
webrtcDisconnectMessage2.setUserid(otherUser.id);
|
|
||||||
|
|
||||||
const serverToClientMessage2 = new ServerToClientMessage();
|
|
||||||
serverToClientMessage2.setWebrtcdisconnectmessage(webrtcDisconnectMessage2);
|
|
||||||
|
|
||||||
if (!user.socket.disconnecting) {
|
|
||||||
user.socket.send(serverToClientMessage2.serializeBinary().buffer, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emitPlayGlobalMessage(client: ExSocketInterface, playglobalmessage: PlayGlobalMessage) {
|
|
||||||
const pusherToBackMessage = new PusherToBackMessage();
|
|
||||||
pusherToBackMessage.setPlayglobalmessage(playglobalmessage);
|
|
||||||
|
|
||||||
client.backConnection.write(pusherToBackMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getWorlds(): Map<string, PusherRoom> {
|
|
||||||
return this.Worlds;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param token
|
|
||||||
*/
|
|
||||||
searchClientByUuid(uuid: string): ExSocketInterface | null {
|
|
||||||
for(const socket of this.sockets.values()){
|
|
||||||
if(socket.userUuid === uuid){
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
public emitSendUserMessage(messageToSend: {userUuid: string, message: string, type: string}): ExSocketInterface {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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[] {
|
|
||||||
const characterLayerObjs: CharacterLayer[] = [];
|
|
||||||
for (const characterLayer of characterLayers) {
|
|
||||||
if (characterLayer.startsWith('customCharacterTexture')) {
|
|
||||||
const customCharacterLayerId: number = +characterLayer.substr(22);
|
|
||||||
for (const memberTexture of memberTextures) {
|
|
||||||
if (memberTexture.id == customCharacterLayerId) {
|
|
||||||
characterLayerObjs.push({
|
|
||||||
name: characterLayer,
|
|
||||||
url: memberTexture.url
|
|
||||||
})
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
characterLayerObjs.push({
|
|
||||||
name: characterLayer,
|
|
||||||
url: undefined
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return characterLayerObjs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public onUserEnters(user: UserDescriptor, listener: ExSocketInterface): void {
|
|
||||||
const subMessage = new SubMessage();
|
|
||||||
subMessage.setUserjoinedmessage(user.toUserJoinedMessage());
|
|
||||||
|
|
||||||
emitInBatch(listener, subMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onUserMoves(user: UserDescriptor, listener: ExSocketInterface): void {
|
|
||||||
const subMessage = new SubMessage();
|
|
||||||
subMessage.setUsermovedmessage(user.toUserMovedMessage());
|
|
||||||
|
|
||||||
emitInBatch(listener, subMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onUserLeaves(userId: number, listener: ExSocketInterface): void {
|
|
||||||
const userLeftMessage = new UserLeftMessage();
|
|
||||||
userLeftMessage.setUserid(userId);
|
|
||||||
|
|
||||||
const subMessage = new SubMessage();
|
|
||||||
subMessage.setUserleftmessage(userLeftMessage);
|
|
||||||
|
|
||||||
emitInBatch(listener, subMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onGroupEnters(group: GroupDescriptor, listener: ExSocketInterface): void {
|
|
||||||
const subMessage = new SubMessage();
|
|
||||||
subMessage.setGroupupdatemessage(group.toGroupUpdateMessage());
|
|
||||||
|
|
||||||
emitInBatch(listener, subMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onGroupMoves(group: GroupDescriptor, listener: ExSocketInterface): void {
|
|
||||||
this.onGroupEnters(group, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onGroupLeaves(groupId: number, listener: ExSocketInterface): void {
|
|
||||||
const groupDeleteMessage = new GroupDeleteMessage();
|
|
||||||
groupDeleteMessage.setGroupid(groupId);
|
|
||||||
|
|
||||||
const subMessage = new SubMessage();
|
|
||||||
subMessage.setGroupdeletemessage(groupDeleteMessage);
|
|
||||||
|
|
||||||
emitInBatch(listener, subMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const socketManager = new SocketManager();
|
|
@ -69,6 +69,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/mkdirp@^1.0.1":
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-1.0.1.tgz#0930b948914a78587de35458b86c907b6e98bbf6"
|
||||||
|
integrity sha512-HkGSK7CGAXncr8Qn/0VqNtExEE+PHMWb+qlR1faHMao7ng6P3tAaoWWBMdva0gL5h4zprjIO89GJOLXsMcDm1Q==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/node@*":
|
"@types/node@*":
|
||||||
version "14.14.11"
|
version "14.14.11"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.11.tgz#fc25a4248a5e8d0837019b1d170146d07334abe0"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.11.tgz#fc25a4248a5e8d0837019b1d170146d07334abe0"
|
||||||
@ -84,6 +91,18 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1"
|
resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1"
|
||||||
integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==
|
integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==
|
||||||
|
|
||||||
|
"@types/uuid@8.3.0":
|
||||||
|
version "8.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f"
|
||||||
|
integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==
|
||||||
|
|
||||||
|
"@types/uuidv4@^5.0.0":
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/uuidv4/-/uuidv4-5.0.0.tgz#2c94e67b0c06d5adb28fb7ced1a1b5f0866ecd50"
|
||||||
|
integrity sha512-xUrhYSJnkTq9CP79cU3svoKTLPCIbMMnu9Twf/tMpHATYSHCAAeDNeb2a/29YORhk5p4atHhCTMsIBU/tvdh6A==
|
||||||
|
dependencies:
|
||||||
|
uuidv4 "*"
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin@^2.26.0":
|
"@typescript-eslint/eslint-plugin@^2.26.0":
|
||||||
version "2.34.0"
|
version "2.34.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9"
|
||||||
@ -1893,6 +1912,19 @@ util-deprecate@~1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||||
|
|
||||||
|
uuid@8.3.2:
|
||||||
|
version "8.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||||
|
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||||
|
|
||||||
|
uuidv4@*, uuidv4@^6.0.7:
|
||||||
|
version "6.2.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuidv4/-/uuidv4-6.2.6.tgz#c37c764b578114b60bdd5460e5578d7d99383ad1"
|
||||||
|
integrity sha512-vFyL4jugB/ln1ux1gXLlBMBv424Dn86EaBMoqUH1K6XI3XuriaWLeRUzH4iWwPu+BOJiw4hc4TjvrPmk+H+ZBQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/uuid" "8.3.0"
|
||||||
|
uuid "8.3.2"
|
||||||
|
|
||||||
v8-compile-cache@^2.0.3:
|
v8-compile-cache@^2.0.3:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132"
|
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132"
|
||||||
|
Loading…
Reference in New Issue
Block a user