2021-12-06 18:20:06 +01:00
|
|
|
import * as rax from "retry-axios";
|
2020-10-13 16:46:46 +02:00
|
|
|
import Axios from "axios";
|
2021-11-12 19:36:13 +01:00
|
|
|
import { CONTACT_URL, PUSHER_URL, DISABLE_ANONYMOUS, OPID_LOGIN_SCREEN_PROVIDER } from "../Enum/EnvironmentVariable";
|
2021-06-23 12:42:24 +02:00
|
|
|
import type { CharacterTexture } from "./LocalUser";
|
2021-08-15 08:51:35 +02:00
|
|
|
import { localUserStore } from "./LocalUserStore";
|
2021-11-29 18:15:21 +01:00
|
|
|
import axios from "axios";
|
2021-12-06 18:20:06 +01:00
|
|
|
import { axiosWithRetry } from "./AxiosUtils";
|
2021-12-16 10:04:41 +01:00
|
|
|
import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
|
|
|
import { isRoomRedirect } from "../Messages/JsonMessages/RoomRedirect";
|
2021-06-03 13:07:52 +02:00
|
|
|
|
2021-06-23 12:42:24 +02:00
|
|
|
export class MapDetail {
|
2022-01-09 13:51:44 +01:00
|
|
|
constructor(public readonly mapUrl: string, public readonly textures: CharacterTexture[] | undefined) { }
|
2021-07-13 19:09:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface RoomRedirect {
|
|
|
|
redirectUrl: string;
|
2021-06-03 13:07:52 +02:00
|
|
|
}
|
2020-10-13 16:46:46 +02:00
|
|
|
|
2020-10-12 16:23:07 +02:00
|
|
|
export class Room {
|
2020-10-13 16:46:46 +02:00
|
|
|
public readonly id: string;
|
2022-01-05 16:30:41 +01:00
|
|
|
/**
|
|
|
|
* @deprecated
|
|
|
|
*/
|
|
|
|
private readonly isPublic: boolean;
|
2021-11-16 13:18:40 +01:00
|
|
|
private _authenticationMandatory: boolean = DISABLE_ANONYMOUS;
|
2021-11-12 19:36:13 +01:00
|
|
|
private _iframeAuthentication?: string = OPID_LOGIN_SCREEN_PROVIDER;
|
2021-07-13 19:09:07 +02:00
|
|
|
private _mapUrl: string | undefined;
|
|
|
|
private _textures: CharacterTexture[] | undefined;
|
2021-06-23 12:42:24 +02:00
|
|
|
private instance: string | undefined;
|
2021-07-13 19:09:07 +02:00
|
|
|
private readonly _search: URLSearchParams;
|
2021-09-21 20:24:53 +02:00
|
|
|
private _contactPage: string | undefined;
|
2021-09-30 16:14:55 +02:00
|
|
|
private _group: string | null = null;
|
2022-01-05 14:32:02 +01:00
|
|
|
private _expireOn: Date | undefined;
|
|
|
|
private _canReport: boolean = false;
|
2020-10-13 16:46:46 +02:00
|
|
|
|
2021-07-13 19:09:07 +02:00
|
|
|
private constructor(private roomUrl: URL) {
|
|
|
|
this.id = roomUrl.pathname;
|
2021-03-30 16:08:49 +02:00
|
|
|
|
2021-07-13 19:09:07 +02:00
|
|
|
if (this.id.startsWith("/")) {
|
2021-03-30 16:08:49 +02:00
|
|
|
this.id = this.id.substr(1);
|
2020-10-13 16:46:46 +02:00
|
|
|
}
|
2021-12-10 01:51:40 +01:00
|
|
|
if (this.id.startsWith("_/") || this.id.startsWith("*/")) {
|
2020-10-13 16:46:46 +02:00
|
|
|
this.isPublic = true;
|
2021-07-13 19:09:07 +02:00
|
|
|
} else if (this.id.startsWith("@/")) {
|
2020-10-13 16:46:46 +02:00
|
|
|
this.isPublic = false;
|
|
|
|
} else {
|
2021-07-13 19:09:07 +02:00
|
|
|
throw new Error("Invalid room ID");
|
|
|
|
}
|
|
|
|
|
|
|
|
this._search = new URLSearchParams(roomUrl.search);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a "Room" object representing the room.
|
|
|
|
* This method will follow room redirects if necessary, so the instance returned is a "real" room.
|
|
|
|
*/
|
|
|
|
public static async createRoom(roomUrl: URL): Promise<Room> {
|
|
|
|
let redirectCount = 0;
|
|
|
|
while (redirectCount < 32) {
|
|
|
|
const room = new Room(roomUrl);
|
|
|
|
const result = await room.getMapDetail();
|
|
|
|
if (result instanceof MapDetail) {
|
|
|
|
return room;
|
|
|
|
}
|
|
|
|
redirectCount++;
|
|
|
|
roomUrl = new URL(result.redirectUrl);
|
2020-10-13 16:46:46 +02:00
|
|
|
}
|
2021-07-13 19:09:07 +02:00
|
|
|
throw new Error("Room resolving seems stuck in a redirect loop after 32 redirect attempts");
|
|
|
|
}
|
2020-10-15 15:50:51 +02:00
|
|
|
|
2021-07-13 19:09:07 +02:00
|
|
|
public static getRoomPathFromExitUrl(exitUrl: string, currentRoomUrl: string): URL {
|
|
|
|
const url = new URL(exitUrl, currentRoomUrl);
|
|
|
|
return url;
|
2020-10-12 16:23:07 +02:00
|
|
|
}
|
2021-01-17 20:34:35 +01:00
|
|
|
|
2021-07-13 19:09:07 +02:00
|
|
|
/**
|
|
|
|
* @deprecated USage of exitSceneUrl is deprecated and therefore, this method is deprecated too.
|
|
|
|
*/
|
|
|
|
public static getRoomPathFromExitSceneUrl(
|
|
|
|
exitSceneUrl: string,
|
|
|
|
currentRoomUrl: string,
|
|
|
|
currentMapUrl: string
|
|
|
|
): URL {
|
|
|
|
const absoluteExitSceneUrl = new URL(exitSceneUrl, currentMapUrl);
|
|
|
|
const baseUrl = new URL(currentRoomUrl);
|
|
|
|
|
|
|
|
const currentRoom = new Room(baseUrl);
|
|
|
|
let instance: string = "global";
|
|
|
|
if (currentRoom.isPublic) {
|
2021-12-07 15:51:30 +01:00
|
|
|
instance = currentRoom.getInstance();
|
2021-07-13 19:09:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
baseUrl.pathname = "/_/" + instance + "/" + absoluteExitSceneUrl.host + absoluteExitSceneUrl.pathname;
|
2022-01-09 23:18:38 +01:00
|
|
|
|
2022-01-09 13:51:44 +01:00
|
|
|
baseUrl.hash = absoluteExitSceneUrl.hash;
|
|
|
|
|
2021-07-13 19:09:07 +02:00
|
|
|
|
|
|
|
return baseUrl;
|
|
|
|
}
|
|
|
|
|
|
|
|
private async getMapDetail(): Promise<MapDetail | RoomRedirect> {
|
2021-11-16 11:14:27 +01:00
|
|
|
try {
|
2021-11-29 18:15:21 +01:00
|
|
|
const result = await axiosWithRetry.get(`${PUSHER_URL}/map`, {
|
2021-11-16 11:14:27 +01:00
|
|
|
params: {
|
|
|
|
playUri: this.roomUrl.toString(),
|
|
|
|
authToken: localUserStore.getAuthToken(),
|
|
|
|
},
|
|
|
|
});
|
2021-07-13 19:09:07 +02:00
|
|
|
|
2021-11-16 11:14:27 +01:00
|
|
|
const data = result.data;
|
2021-12-14 18:55:41 +01:00
|
|
|
|
2021-12-30 10:19:32 +01:00
|
|
|
if (data.authenticationMandatory !== undefined) {
|
|
|
|
data.authenticationMandatory = Boolean(data.authenticationMandatory);
|
|
|
|
}
|
|
|
|
|
2021-12-23 14:53:03 +01:00
|
|
|
if (isRoomRedirect(data)) {
|
2021-11-16 11:14:27 +01:00
|
|
|
return {
|
2021-12-23 14:53:03 +01:00
|
|
|
redirectUrl: data.redirectUrl,
|
2021-11-16 11:14:27 +01:00
|
|
|
};
|
2021-12-14 18:55:41 +01:00
|
|
|
} else if (isMapDetailsData(data)) {
|
|
|
|
console.log("Map ", this.id, " resolves to URL ", data.mapUrl);
|
|
|
|
this._mapUrl = data.mapUrl;
|
|
|
|
this._textures = data.textures;
|
|
|
|
this._group = data.group;
|
|
|
|
this._authenticationMandatory =
|
|
|
|
data.authenticationMandatory != null ? data.authenticationMandatory : DISABLE_ANONYMOUS;
|
|
|
|
this._iframeAuthentication = data.iframeAuthentication || OPID_LOGIN_SCREEN_PROVIDER;
|
|
|
|
this._contactPage = data.contactPage || CONTACT_URL;
|
2022-01-05 14:32:02 +01:00
|
|
|
if (data.expireOn) {
|
|
|
|
this._expireOn = new Date(data.expireOn);
|
|
|
|
}
|
|
|
|
this._canReport = data.canReport ?? false;
|
2021-12-14 18:55:41 +01:00
|
|
|
return new MapDetail(data.mapUrl, data.textures);
|
|
|
|
} else {
|
2021-12-16 10:04:41 +01:00
|
|
|
throw new Error("Data received by the /map endpoint of the Pusher is not in a valid format.");
|
2021-11-16 11:14:27 +01:00
|
|
|
}
|
|
|
|
} catch (e) {
|
2021-12-08 14:46:23 +01:00
|
|
|
if (axios.isAxiosError(e) && e.response?.status == 401 && e.response?.data === "Token decrypted error") {
|
|
|
|
console.warn("JWT token sent could not be decrypted. Maybe it expired?");
|
2021-11-16 11:14:27 +01:00
|
|
|
localUserStore.setAuthToken(null);
|
|
|
|
window.location.assign("/login");
|
2021-12-14 18:55:41 +01:00
|
|
|
} else if (axios.isAxiosError(e)) {
|
2021-12-08 14:46:23 +01:00
|
|
|
console.error("Error => getMapDetail", e, e.response);
|
2021-12-14 18:55:41 +01:00
|
|
|
} else {
|
|
|
|
console.error("Error => getMapDetail", e);
|
2021-11-16 11:14:27 +01:00
|
|
|
}
|
|
|
|
throw e;
|
2021-07-13 19:09:07 +02:00
|
|
|
}
|
2020-10-13 16:46:46 +02:00
|
|
|
}
|
|
|
|
|
2020-10-13 18:44:50 +02:00
|
|
|
/**
|
|
|
|
* Instance name is:
|
|
|
|
* - In a public URL: the second part of the URL ( _/[instance]/map.json)
|
|
|
|
* - In a private URL: [organizationId/worldId]
|
2022-01-05 16:30:41 +01:00
|
|
|
*
|
|
|
|
* @deprecated
|
2020-10-13 18:44:50 +02:00
|
|
|
*/
|
|
|
|
public getInstance(): string {
|
|
|
|
if (this.instance !== undefined) {
|
|
|
|
return this.instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.isPublic) {
|
2021-12-10 01:51:40 +01:00
|
|
|
const match = /[_*]\/([^/]+)\/.+/.exec(this.id);
|
2021-06-23 12:42:24 +02:00
|
|
|
if (!match) throw new Error('Could not extract instance from "' + this.id + '"');
|
2020-10-13 18:44:50 +02:00
|
|
|
this.instance = match[1];
|
|
|
|
return this.instance;
|
|
|
|
} else {
|
2020-10-14 10:37:00 +02:00
|
|
|
const match = /@\/([^/]+)\/([^/]+)\/.+/.exec(this.id);
|
2021-06-23 12:42:24 +02:00
|
|
|
if (!match) throw new Error('Could not extract instance from "' + this.id + '"');
|
2021-07-13 19:09:07 +02:00
|
|
|
this.instance = match[1] + "/" + match[2];
|
2020-10-13 18:44:50 +02:00
|
|
|
return this.instance;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-23 12:42:24 +02:00
|
|
|
public isDisconnected(): boolean {
|
2021-07-13 19:09:07 +02:00
|
|
|
const alone = this._search.get("alone");
|
|
|
|
if (alone && alone !== "0" && alone.toLowerCase() !== "false") {
|
2021-03-30 16:08:49 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public get search(): URLSearchParams {
|
|
|
|
return this._search;
|
|
|
|
}
|
2021-07-13 19:09:07 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 2 rooms are equal if they share the same path (but not necessarily the same hash)
|
|
|
|
* @param room
|
|
|
|
*/
|
|
|
|
public isEqual(room: Room): boolean {
|
|
|
|
return room.key === this.key;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A key representing this room
|
|
|
|
*/
|
|
|
|
public get key(): string {
|
|
|
|
const newUrl = new URL(this.roomUrl.toString());
|
2021-07-20 18:29:41 +02:00
|
|
|
newUrl.search = "";
|
2021-07-13 19:09:07 +02:00
|
|
|
newUrl.hash = "";
|
|
|
|
return newUrl.toString();
|
|
|
|
}
|
|
|
|
|
2021-11-24 19:24:43 +01:00
|
|
|
public get href(): string {
|
|
|
|
return this.roomUrl.toString();
|
|
|
|
}
|
|
|
|
|
2021-07-13 19:09:07 +02:00
|
|
|
get textures(): CharacterTexture[] | undefined {
|
|
|
|
return this._textures;
|
|
|
|
}
|
|
|
|
|
|
|
|
get mapUrl(): string {
|
|
|
|
if (!this._mapUrl) {
|
|
|
|
throw new Error("Map URL not fetched yet");
|
|
|
|
}
|
|
|
|
return this._mapUrl;
|
|
|
|
}
|
2021-09-05 18:17:49 +02:00
|
|
|
|
|
|
|
get authenticationMandatory(): boolean {
|
|
|
|
return this._authenticationMandatory;
|
|
|
|
}
|
|
|
|
|
|
|
|
get iframeAuthentication(): string | undefined {
|
|
|
|
return this._iframeAuthentication;
|
|
|
|
}
|
2021-09-21 20:24:53 +02:00
|
|
|
|
|
|
|
get contactPage(): string | undefined {
|
|
|
|
return this._contactPage;
|
|
|
|
}
|
2021-09-30 16:14:55 +02:00
|
|
|
|
|
|
|
get group(): string | null {
|
|
|
|
return this._group;
|
|
|
|
}
|
2022-01-05 14:32:02 +01:00
|
|
|
|
|
|
|
get expireOn(): Date | undefined {
|
|
|
|
return this._expireOn;
|
|
|
|
}
|
|
|
|
|
|
|
|
get canReport(): boolean {
|
|
|
|
return this._canReport;
|
|
|
|
}
|
2020-10-13 16:46:46 +02:00
|
|
|
}
|