Merging with develop

This commit is contained in:
David Négrier 2021-07-23 16:41:38 +02:00
parent 3d5c222957
commit cdd61bdb2c
135 changed files with 4313 additions and 2128 deletions

View file

@ -39,9 +39,7 @@ export class AuthenticateController extends BaseController {
if (typeof organizationMemberToken != "string") throw new Error("No organization token");
const data = await adminApi.fetchMemberDataByToken(organizationMemberToken);
const userUuid = data.userUuid;
const organizationSlug = data.organizationSlug;
const worldSlug = data.worldSlug;
const roomSlug = data.roomSlug;
const roomUrl = data.roomUrl;
const mapUrlStart = data.mapUrlStart;
const textures = data.textures;
@ -52,9 +50,7 @@ export class AuthenticateController extends BaseController {
JSON.stringify({
authToken,
userUuid,
organizationSlug,
worldSlug,
roomSlug,
roomUrl,
mapUrlStart,
organizationMemberToken,
textures,

View file

@ -16,7 +16,7 @@ export class DebugController {
const query = parse(req.getQuery());
if (query.token !== ADMIN_API_TOKEN) {
return res.status(401).send("Invalid token sent!");
return res.writeStatus("401 Unauthorized").end("Invalid token sent!");
}
return res

View file

@ -17,18 +17,20 @@ import {
ServerToClientMessage,
CompanionMessage,
EmotePromptMessage,
VariableMessage,
} from "../Messages/generated/messages_pb";
import { UserMovesMessage } from "../Messages/generated/messages_pb";
import { TemplatedApp } from "uWebSockets.js";
import { parse } from "query-string";
import { jwtTokenManager } from "../Services/JWTTokenManager";
import { adminApi, CharacterTexture, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
import { SocketManager, socketManager } from "../Services/SocketManager";
import { emitInBatch } from "../Services/IoSocketHelpers";
import { ADMIN_API_TOKEN, ADMIN_API_URL, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable";
import { Zone } from "_Model/Zone";
import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface";
import { v4 } from "uuid";
import { CharacterTexture } from "../Services/AdminApi/CharacterTexture";
export class IoSocketController {
private nextUserId: number = 1;
@ -221,14 +223,12 @@ export class IoSocketController {
memberVisitCardUrl = userData.visitCardUrl;
memberTextures = userData.textures;
if (
!room.public &&
room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY &&
(userData.anonymous === true || !room.canAccess(memberTags))
) {
throw new Error("Insufficient privileges to access this room");
}
if (
!room.public &&
room.policyType === GameRoomPolicyTypes.MEMBERS_ONLY_POLICY &&
userData.anonymous === true
) {
@ -358,6 +358,8 @@ export class IoSocketController {
socketManager.handleSilentMessage(client, message.getSilentmessage() as SilentMessage);
} else if (message.hasItemeventmessage()) {
socketManager.handleItemEvent(client, message.getItemeventmessage() as ItemEventMessage);
} else if (message.hasVariablemessage()) {
socketManager.handleVariableEvent(client, message.getVariablemessage() as VariableMessage);
} else if (message.hasWebrtcsignaltoservermessage()) {
socketManager.emitVideo(
client,

View file

@ -2,6 +2,9 @@ import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
import { BaseController } from "./BaseController";
import { parse } from "query-string";
import { adminApi } from "../Services/AdminApi";
import { ADMIN_API_URL } from "../Enum/EnvironmentVariable";
import { GameRoomPolicyTypes } from "../Model/PusherRoom";
import { MapDetailsData } from "../Services/AdminApi/MapDetailsData";
export class MapController extends BaseController {
constructor(private App: TemplatedApp) {
@ -25,35 +28,46 @@ export class MapController extends BaseController {
const query = parse(req.getQuery());
if (typeof query.organizationSlug !== "string") {
console.error("Expected organizationSlug parameter");
if (typeof query.playUri !== "string") {
console.error("Expected playUri parameter in /map endpoint");
res.writeStatus("400 Bad request");
this.addCorsHeaders(res);
res.end("Expected organizationSlug parameter");
res.end("Expected playUri parameter");
return;
}
if (typeof query.worldSlug !== "string") {
console.error("Expected worldSlug parameter");
res.writeStatus("400 Bad request");
// If no admin URL is set, let's react on '/_/[instance]/[map url]' URLs
if (!ADMIN_API_URL) {
const roomUrl = new URL(query.playUri);
const match = /\/_\/[^/]+\/(.+)/.exec(roomUrl.pathname);
if (!match) {
res.writeStatus("404 Not Found");
this.addCorsHeaders(res);
res.end(JSON.stringify({}));
return;
}
const mapUrl = roomUrl.protocol + "//" + match[1];
res.writeStatus("200 OK");
this.addCorsHeaders(res);
res.end("Expected worldSlug parameter");
return;
}
if (typeof query.roomSlug !== "string" && query.roomSlug !== undefined) {
console.error("Expected only one roomSlug parameter");
res.writeStatus("400 Bad request");
this.addCorsHeaders(res);
res.end("Expected only one roomSlug parameter");
res.end(
JSON.stringify({
mapUrl,
policy_type: GameRoomPolicyTypes.ANONYMOUS_POLICY,
roomSlug: "", // Deprecated
tags: [],
textures: [],
} as MapDetailsData)
);
return;
}
(async () => {
try {
const mapDetails = await adminApi.fetchMapDetails(
query.organizationSlug as string,
query.worldSlug as string,
query.roomSlug as string | undefined
);
const mapDetails = await adminApi.fetchMapDetails(query.playUri as string);
res.writeStatus("200 OK");
this.addCorsHeaders(res);

View file

@ -1,50 +1,66 @@
import { ExSocketInterface } from "_Model/Websocket/ExSocketInterface";
import { PositionDispatcher } from "./PositionDispatcher";
import { ViewportInterface } from "_Model/Websocket/ViewportMessage";
import { extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous } from "./RoomIdentifier";
import { arrayIntersect } from "../Services/ArrayHelper";
import { ZoneEventListener } from "_Model/Zone";
import { GroupDescriptor, UserDescriptor, ZoneEventListener } from "_Model/Zone";
import { apiClientRepository } from "../Services/ApiClientRepository";
import {
BatchToPusherMessage,
BatchToPusherRoomMessage,
EmoteEventMessage,
ErrorMessage,
GroupLeftZoneMessage,
GroupUpdateZoneMessage,
RoomMessage,
SubMessage,
UserJoinedZoneMessage,
UserLeftZoneMessage,
UserMovedMessage,
VariableMessage,
VariableWithTagMessage,
ZoneMessage,
} from "../Messages/generated/messages_pb";
import Debug from "debug";
import { ClientReadableStream } from "grpc";
import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface";
const debug = Debug("room");
export enum GameRoomPolicyTypes {
ANONYMUS_POLICY = 1,
ANONYMOUS_POLICY = 1,
MEMBERS_ONLY_POLICY,
USE_TAGS_POLICY,
}
export class PusherRoom {
private readonly positionNotifier: PositionDispatcher;
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;
private backConnection!: ClientReadableStream<BatchToPusherRoomMessage>;
private isClosing: boolean = false;
private listeners: Set<ExSocketInterface> = new Set<ExSocketInterface>();
//public readonly variables = new Map<string, string>();
constructor(public readonly roomId: string, private socketListener: ZoneEventListener) {
this.public = isRoomAnonymous(roomId);
constructor(public readonly roomUrl: string, private socketListener: ZoneEventListener) {
this.tags = [];
this.policyType = GameRoomPolicyTypes.ANONYMUS_POLICY;
if (this.public) {
this.roomSlug = extractRoomSlugPublicRoomId(this.roomId);
} else {
const { organizationSlug, worldSlug, roomSlug } = extractDataFromPrivateRoomId(this.roomId);
this.roomSlug = roomSlug;
this.organizationSlug = organizationSlug;
this.worldSlug = worldSlug;
}
this.policyType = GameRoomPolicyTypes.ANONYMOUS_POLICY;
// A zone is 10 sprites wide.
this.positionNotifier = new PositionDispatcher(this.roomId, 320, 320, this.socketListener);
this.positionNotifier = new PositionDispatcher(this.roomUrl, 320, 320, this.socketListener);
}
public setViewport(socket: ExSocketInterface, viewport: ViewportInterface): void {
this.positionNotifier.setViewport(socket, viewport);
}
public join(socket: ExSocketInterface) {
this.listeners.add(socket);
}
public leave(socket: ExSocketInterface) {
this.positionNotifier.removeViewport(socket);
this.listeners.delete(socket);
}
public canAccess(userTags: string[]): boolean {
@ -63,4 +79,75 @@ export class PusherRoom {
return false;
}
}
/**
* Creates a connection to the back server to track global messages relative to this room (like variable changes).
*/
public async init(): Promise<void> {
debug("Opening connection to room %s on back server", this.roomUrl);
const apiClient = await apiClientRepository.getClient(this.roomUrl);
const roomMessage = new RoomMessage();
roomMessage.setRoomid(this.roomUrl);
this.backConnection = apiClient.listenRoom(roomMessage);
this.backConnection.on("data", (batch: BatchToPusherRoomMessage) => {
for (const message of batch.getPayloadList()) {
if (message.hasVariablemessage()) {
const variableMessage = message.getVariablemessage() as VariableWithTagMessage;
const readableBy = variableMessage.getReadableby();
// We need to store all variables to dispatch variables later to the listeners
//this.variables.set(variableMessage.getName(), variableMessage.getValue(), readableBy);
// Let's dispatch this variable to all the listeners
for (const listener of this.listeners) {
const subMessage = new SubMessage();
if (!readableBy || listener.tags.includes(readableBy)) {
subMessage.setVariablemessage(variableMessage);
}
listener.emitInBatch(subMessage);
}
} else if (message.hasErrormessage()) {
const errorMessage = message.getErrormessage() as ErrorMessage;
// Let's dispatch this error to all the listeners
for (const listener of this.listeners) {
const subMessage = new SubMessage();
subMessage.setErrormessage(errorMessage);
listener.emitInBatch(subMessage);
}
} else {
throw new Error("Unexpected message");
}
}
});
this.backConnection.on("error", (e) => {
if (!this.isClosing) {
debug("Error on back connection");
this.close();
// Let's close all connections linked to that room
for (const listener of this.listeners) {
listener.disconnecting = true;
listener.end(1011, "Connection error between pusher and back server");
}
}
});
this.backConnection.on("close", () => {
if (!this.isClosing) {
debug("Close on back connection");
this.close();
// Let's close all connections linked to that room
for (const listener of this.listeners) {
listener.disconnecting = true;
listener.end(1011, "Connection closed between pusher and back server");
}
}
});
}
public close(): void {
debug("Closing connection to room %s on back server", this.roomUrl);
this.isClosing = true;
this.backConnection.cancel();
}
}

View file

@ -1,30 +0,0 @@
//helper functions to parse room IDs
export const isRoomAnonymous = (roomID: string): boolean => {
if (roomID.startsWith("_/")) {
return true;
} else if (roomID.startsWith("@/")) {
return false;
} else {
throw new Error("Incorrect room ID: " + roomID);
}
};
export const extractRoomSlugPublicRoomId = (roomId: string): string => {
const idParts = roomId.split("/");
if (idParts.length < 3) throw new Error("Incorrect roomId: " + roomId);
return idParts.slice(2).join("/");
};
export interface extractDataFromPrivateRoomIdResponse {
organizationSlug: string;
worldSlug: string;
roomSlug: string;
}
export const extractDataFromPrivateRoomId = (roomId: string): extractDataFromPrivateRoomIdResponse => {
const idParts = roomId.split("/");
if (idParts.length < 4) throw new Error("Incorrect roomId: " + roomId);
const organizationSlug = idParts[1];
const worldSlug = idParts[2];
const roomSlug = idParts[3];
return { organizationSlug, worldSlug, roomSlug };
};

View file

@ -10,7 +10,6 @@ import {
SubMessage,
} from "../../Messages/generated/messages_pb";
import { WebSocket } from "uWebSockets.js";
import { CharacterTexture } from "../../Services/AdminApi";
import { ClientDuplexStream } from "grpc";
import { Zone } from "_Model/Zone";

View file

@ -9,9 +9,9 @@ import {
SubMessage,
} from "../../Messages/generated/messages_pb";
import { WebSocket } from "uWebSockets.js";
import { CharacterTexture } from "../../Services/AdminApi";
import { ClientDuplexStream } from "grpc";
import { Zone } from "_Model/Zone";
import { CharacterTexture } from "../../Services/AdminApi/CharacterTexture";
export type BackConnection = ClientDuplexStream<PusherToBackMessage, ServerToClientMessage>;

View file

@ -15,6 +15,7 @@ import {
ZoneMessage,
EmoteEventMessage,
CompanionMessage,
ErrorMessage,
} from "../Messages/generated/messages_pb";
import { ClientReadableStream } from "grpc";
import { PositionDispatcher } from "_Model/PositionDispatcher";
@ -30,6 +31,7 @@ export interface ZoneEventListener {
onGroupMoves(group: GroupDescriptor, listener: ExSocketInterface): void;
onGroupLeaves(groupId: number, listener: ExSocketInterface): void;
onEmote(emoteMessage: EmoteEventMessage, listener: ExSocketInterface): void;
onError(errorMessage: ErrorMessage, listener: ExSocketInterface): void;
}
/*export type EntersCallback = (thing: Movable, listener: User) => void;
@ -39,6 +41,7 @@ export type LeavesCallback = (thing: Movable, listener: User) => void;*/
export class UserDescriptor {
private constructor(
public readonly userId: number,
private userUuid: string,
private name: string,
private characterLayers: CharacterLayerMessage[],
private position: PositionMessage,
@ -57,6 +60,7 @@ export class UserDescriptor {
}
return new UserDescriptor(
message.getUserid(),
message.getUseruuid(),
message.getName(),
message.getCharacterlayersList(),
position,
@ -84,6 +88,7 @@ export class UserDescriptor {
userJoinedMessage.setVisitcardurl(this.visitCardUrl);
}
userJoinedMessage.setCompanion(this.companion);
userJoinedMessage.setUseruuid(this.userUuid);
return userJoinedMessage;
}
@ -214,6 +219,9 @@ export class Zone {
} else if (message.hasEmoteeventmessage()) {
const emoteEventMessage = message.getEmoteeventmessage() as EmoteEventMessage;
this.notifyEmote(emoteEventMessage);
} else if (message.hasErrormessage()) {
const errorMessage = message.getErrormessage() as ErrorMessage;
this.notifyError(errorMessage);
} else {
throw new Error("Unexpected message");
}
@ -300,6 +308,12 @@ export class Zone {
}
}
private notifyError(errorMessage: ErrorMessage) {
for (const listener of this.listeners) {
this.socketListener.onError(errorMessage, listener);
}
}
/**
* Notify listeners of this zone that this group left
*/

View file

@ -1,11 +1,12 @@
import { ADMIN_API_TOKEN, ADMIN_API_URL } from "../Enum/EnvironmentVariable";
import Axios from "axios";
import { GameRoomPolicyTypes } from "_Model/PusherRoom";
import { CharacterTexture } from "./AdminApi/CharacterTexture";
import { MapDetailsData } from "./AdminApi/MapDetailsData";
import { RoomRedirect } from "./AdminApi/RoomRedirect";
export interface AdminApiData {
organizationSlug: string;
worldSlug: string;
roomSlug: string;
roomUrl: string;
mapUrlStart: string;
tags: string[];
policy_type: number;
@ -14,25 +15,11 @@ 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;
url: string;
rights: string;
}
export interface FetchMemberDataByUuidResponse {
uuid: string;
tags: string[];
@ -43,24 +30,15 @@ export interface FetchMemberDataByUuidResponse {
}
class AdminApi {
async fetchMapDetails(
organizationSlug: string,
worldSlug: string,
roomSlug: string | undefined
): Promise<MapDetailsData> {
async fetchMapDetails(playUri: string): Promise<MapDetailsData | RoomRedirect> {
if (!ADMIN_API_URL) {
return Promise.reject(new Error("No admin backoffice set!"));
}
const params: { organizationSlug: string; worldSlug: string; roomSlug?: string } = {
organizationSlug,
worldSlug,
const params: { playUri: string } = {
playUri,
};
if (roomSlug) {
params.roomSlug = roomSlug;
}
const res = await Axios.get(ADMIN_API_URL + "/api/map", {
headers: { Authorization: `${ADMIN_API_TOKEN}` },
params,
@ -121,26 +99,20 @@ class AdminApi {
);
}
async verifyBanUser(
organizationMemberToken: string,
ipAddress: string,
organization: string,
world: string
): Promise<AdminBannedData> {
async verifyBanUser(userUuid: string, ipAddress: string, roomUrl: 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 +
"/api/ban" +
"?ipAddress=" +
ipAddress +
encodeURIComponent(ipAddress) +
"&token=" +
organizationMemberToken,
encodeURIComponent(userUuid) +
"&roomUrl=" +
encodeURIComponent(roomUrl),
{ headers: { Authorization: `${ADMIN_API_TOKEN}` } }
).then((data) => {
return data.data;

View file

@ -0,0 +1,11 @@
import * as tg from "generic-type-guard";
export const isCharacterTexture = new tg.IsInterface()
.withProperties({
id: tg.isNumber,
level: tg.isNumber,
url: tg.isString,
rights: tg.isString,
})
.get();
export type CharacterTexture = tg.GuardedType<typeof isCharacterTexture>;

View file

@ -0,0 +1,20 @@
import * as tg from "generic-type-guard";
import { GameRoomPolicyTypes } from "_Model/PusherRoom";
import { isCharacterTexture } from "./CharacterTexture";
import { isAny, isNumber } from "generic-type-guard";
/*const isNumericEnum =
<T extends { [n: number]: string }>(vs: T) =>
(v: any): v is T =>
typeof v === "number" && v in vs;*/
export const isMapDetailsData = new tg.IsInterface()
.withProperties({
roomSlug: tg.isOptional(tg.isString), // deprecated
mapUrl: tg.isString,
policy_type: isNumber, //isNumericEnum(GameRoomPolicyTypes),
tags: tg.isArray(tg.isString),
textures: tg.isArray(isCharacterTexture),
})
.get();
export type MapDetailsData = tg.GuardedType<typeof isMapDetailsData>;

View file

@ -0,0 +1,8 @@
import * as tg from "generic-type-guard";
export const isRoomRedirect = new tg.IsInterface()
.withProperties({
redirectUrl: tg.isString,
})
.get();
export type RoomRedirect = tg.GuardedType<typeof isRoomRedirect>;

View file

@ -9,7 +9,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, ipAddress?: string, room?: string): Promise<string> {
public async getUserUuidFromToken(token: unknown, ipAddress?: string, roomUrl?: string): Promise<string> {
if (!token) {
throw new Error("An authentication error happened, a user tried to connect without a token.");
}
@ -50,8 +50,8 @@ class JWTTokenManager {
if (ADMIN_API_URL) {
//verify user in admin
let promise = new Promise((resolve) => resolve());
if (ipAddress && room) {
promise = this.verifyBanUser(tokenInterface.userUuid, ipAddress, room);
if (ipAddress && roomUrl) {
promise = this.verifyBanUser(tokenInterface.userUuid, ipAddress, roomUrl);
}
promise
.then(() => {
@ -79,19 +79,9 @@ 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];
private verifyBanUser(userUuid: string, ipAddress: string, roomUrl: string): Promise<AdminBannedData> {
return adminApi
.verifyBanUser(userUuid, ipAddress, organization, world)
.verifyBanUser(userUuid, ipAddress, roomUrl)
.then((data: AdminBannedData) => {
if (data && data.is_banned) {
throw new Error("User was banned");

View file

@ -30,10 +30,12 @@ import {
BanMessage,
RefreshRoomMessage,
EmotePromptMessage,
VariableMessage,
ErrorMessage,
} from "../Messages/generated/messages_pb";
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
import { JITSI_ISS, SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable";
import { adminApi, CharacterTexture } from "./AdminApi";
import { ADMIN_API_URL, JITSI_ISS, SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable";
import { adminApi } from "./AdminApi";
import { emitInBatch } from "./IoSocketHelpers";
import Jwt from "jsonwebtoken";
import { JITSI_URL } from "../Enum/EnvironmentVariable";
@ -44,6 +46,8 @@ import { GroupDescriptor, UserDescriptor, ZoneEventListener } from "_Model/Zone"
import Debug from "debug";
import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface";
import { WebSocket } from "uWebSockets.js";
import { isRoomRedirect } from "./AdminApi/RoomRedirect";
import { CharacterTexture } from "./AdminApi/CharacterTexture";
const debug = Debug("socket");
@ -61,7 +65,6 @@ export interface AdminSocketData {
export class SocketManager implements ZoneEventListener {
private rooms: Map<string, PusherRoom> = new Map<string, PusherRoom>();
private sockets: Map<number, ExSocketInterface> = new Map<number, ExSocketInterface>();
constructor() {
clientEventsEmitter.registerToClientJoin((clientUUid: string, roomId: string) => {
@ -191,8 +194,6 @@ export class SocketManager implements ZoneEventListener {
.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);
@ -227,6 +228,9 @@ export class SocketManager implements ZoneEventListener {
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setJoinroommessage(joinRoomMessage);
streamToPusher.write(pusherToBackMessage);
const pusherRoom = await this.getOrCreateRoom(client.roomId);
pusherRoom.join(client);
} catch (e) {
console.error('An error occurred on "join_room" event');
console.error(e);
@ -278,6 +282,13 @@ export class SocketManager implements ZoneEventListener {
emitInBatch(listener, subMessage);
}
onError(errorMessage: ErrorMessage, listener: ExSocketInterface): void {
const subMessage = new SubMessage();
subMessage.setErrormessage(errorMessage);
emitInBatch(listener, subMessage);
}
// Useless now, will be useful again if we allow editing details in game
handleSetPlayerDetails(client: ExSocketInterface, playerDetailsMessage: SetPlayerDetailsMessage) {
const pusherToBackMessage = new PusherToBackMessage();
@ -300,16 +311,17 @@ export class SocketManager implements ZoneEventListener {
client.backConnection.write(pusherToBackMessage);
}
handleVariableEvent(client: ExSocketInterface, variableMessage: VariableMessage) {
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setVariablemessage(variableMessage);
client.backConnection.write(pusherToBackMessage);
}
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
//todo: move to back because this fail if the reported player is in another pusher.
await adminApi.reportPlayer(
reportedSocket.userUuid,
reportPlayerMessage.getReporteduseruuid(),
reportPlayerMessage.getReportcomment(),
client.userUuid,
client.roomId.split("/")[2]
@ -334,14 +346,6 @@ export class SocketManager implements ZoneEventListener {
socket.backConnection.write(pusherToBackMessage);
}
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 {
@ -354,6 +358,7 @@ export class SocketManager implements ZoneEventListener {
room.leave(socket);
if (room.isEmpty()) {
room.close();
this.rooms.delete(socket.roomId);
debug("Room %s is empty. Deleting.", socket.roomId);
}
@ -364,9 +369,8 @@ export class SocketManager implements ZoneEventListener {
//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)");
console.log("A user left");
}
}
} finally {
@ -376,23 +380,30 @@ export class SocketManager implements ZoneEventListener {
}
}
async getOrCreateRoom(roomId: string): Promise<PusherRoom> {
async getOrCreateRoom(roomUrl: string): Promise<PusherRoom> {
//check and create new world for a room
let world = this.rooms.get(roomId);
if (world === undefined) {
world = new PusherRoom(roomId, this);
if (!world.public) {
await this.updateRoomWithAdminData(world);
let room = this.rooms.get(roomUrl);
if (room === undefined) {
room = new PusherRoom(roomUrl, this);
if (ADMIN_API_URL) {
await this.updateRoomWithAdminData(room);
}
this.rooms.set(roomId, world);
await room.init();
this.rooms.set(roomUrl, room);
}
return Promise.resolve(world);
return room;
}
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);
public async updateRoomWithAdminData(room: PusherRoom): Promise<void> {
const data = await adminApi.fetchMapDetails(room.roomUrl);
if (isRoomRedirect(data)) {
// TODO: if the updated room data is actually a redirect, we need to take everybody on the map
// and redirect everybody to the new location (so we need to close the connection for everybody)
} else {
room.tags = data.tags;
room.policyType = Number(data.policy_type);
}
}
emitPlayGlobalMessage(client: ExSocketInterface, playglobalmessage: PlayGlobalMessage) {
@ -410,15 +421,6 @@ export class SocketManager implements ZoneEventListener {
return this.rooms;
}
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) {
try {
const room = queryJitsiJwtMessage.getJitsiroom();

View file

@ -1,19 +0,0 @@
import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "../src/Model/RoomIdentifier";
describe("RoomIdentifier", () => {
it("should flag public id as anonymous", () => {
expect(isRoomAnonymous('_/global/test')).toBe(true);
});
it("should flag public id as not anonymous", () => {
expect(isRoomAnonymous('@/afup/afup2020/1floor')).toBe(false);
});
it("should extract roomSlug from public ID", () => {
expect(extractRoomSlugPublicRoomId('_/global/npeguin/test.json')).toBe('npeguin/test.json');
});
it("should extract correct from private ID", () => {
const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId('@/afup/afup2020/1floor');
expect(organizationSlug).toBe('afup');
expect(worldSlug).toBe('afup2020');
expect(roomSlug).toBe('1floor');
});
})

View file

@ -3,7 +3,7 @@
"experimentalDecorators": true,
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"downlevelIteration": true,
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */