From b89997c9f1a69e5da82b107a47291b53866d4b6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 15 Apr 2022 21:12:00 +0200 Subject: [PATCH] Changes the prefix added in front of the jitsiRoomName Previously, the prefix was computed using the org/world (in SAAS) or the instance part of public URLs. Neither was guaranteeing the Jitsi Room would be unique accross rooms. The new system computes a hash of the room URL and prepends it to the jitsi room name. BREAKING CHANGE: this means the URL of the Jitsi room will change for all maps. Users having bookmarked the Jitsi room (for instance in the Jitsi mobile app) will need to update their bookmarks. --- back/src/Model/GameRoom.ts | 6 ++---- front/src/Connexion/Room.ts | 9 --------- .../Phaser/Game/GameMapPropertiesListener.ts | 2 +- front/src/Phaser/Game/GameScene.ts | 2 -- front/src/Utils/StringUtils.ts | 17 +++++++++++++++++ front/src/WebRtc/JitsiFactory.ts | 7 ++++--- messages/JsonMessages/MapDetailsData.ts | 1 - pusher/src/Controller/MapController.ts | 10 ++-------- 8 files changed, 26 insertions(+), 28 deletions(-) diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index e12e307b..5b1e13bf 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -562,14 +562,13 @@ export class GameRoom { if (!ADMIN_API_URL) { const roomUrlObj = new URL(roomUrl); - const match = /\/_\/([^/]+)\/(.+)/.exec(roomUrlObj.pathname); + const match = /\/_\/[^/]+\/(.+)/.exec(roomUrlObj.pathname); if (!match) { console.error("Unexpected room URL", roomUrl); throw new Error('Unexpected room URL "' + roomUrl + '"'); } - const instance = match[1]; - const mapUrl = roomUrlObj.protocol + "//" + match[2]; + const mapUrl = roomUrlObj.protocol + "//" + match[1]; return { mapUrl, @@ -579,7 +578,6 @@ export class GameRoom { roomSlug: null, contactPage: null, group: null, - instance, }; } diff --git a/front/src/Connexion/Room.ts b/front/src/Connexion/Room.ts index 9a2a3e52..4e896572 100644 --- a/front/src/Connexion/Room.ts +++ b/front/src/Connexion/Room.ts @@ -18,7 +18,6 @@ export class Room { private _authenticationMandatory: boolean = DISABLE_ANONYMOUS; private _iframeAuthentication?: string = OPID_LOGIN_SCREEN_PROVIDER; private _mapUrl: string | undefined; - private _instance: string | undefined; private readonly _search: URLSearchParams; private _contactPage: string | undefined; private _group: string | null = null; @@ -121,7 +120,6 @@ export class Room { this._canReport = data.canReport ?? false; this._loadingLogo = data.loadingLogo ?? undefined; this._loginSceneLogo = data.loginSceneLogo ?? undefined; - this._instance = data.instance; return new MapDetail(data.mapUrl); } else { console.log(data); @@ -200,13 +198,6 @@ export class Room { return this._group; } - get instance(): string { - if (!this._instance) { - throw new Error("Instance not fetched yet"); - } - return this._instance; - } - get expireOn(): Date | undefined { return this._expireOn; } diff --git a/front/src/Phaser/Game/GameMapPropertiesListener.ts b/front/src/Phaser/Game/GameMapPropertiesListener.ts index 1dffd434..449e8708 100644 --- a/front/src/Phaser/Game/GameMapPropertiesListener.ts +++ b/front/src/Phaser/Game/GameMapPropertiesListener.ts @@ -67,7 +67,7 @@ export class GameMapPropertiesListener { }); } else { const openJitsiRoomFunction = () => { - const roomName = jitsiFactory.getRoomName(newValue.toString(), this.scene.instance); + const roomName = jitsiFactory.getRoomName(newValue.toString(), this.scene.roomUrl); const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined; if (JITSI_PRIVATE_MODE && !jitsiUrl) { diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index ffef7588..2d2b6d03 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -185,7 +185,6 @@ export class GameScene extends DirtyScene { private biggestAvailableAreaStoreUnsubscribe!: () => void; MapUrlFile: string; roomUrl: string; - instance: string; currentTick!: number; lastSentTick!: number; // The last tick at which a position was sent. @@ -234,7 +233,6 @@ export class GameScene extends DirtyScene { }); this.Terrains = []; this.groups = new Map(); - this.instance = room.instance; this.MapUrlFile = MapUrlFile; this.roomUrl = room.key; diff --git a/front/src/Utils/StringUtils.ts b/front/src/Utils/StringUtils.ts index 1f168c15..8c5f16db 100644 --- a/front/src/Utils/StringUtils.ts +++ b/front/src/Utils/StringUtils.ts @@ -9,4 +9,21 @@ export class StringUtils { } return { x: values[0], y: values[1] }; } + + /** + * Computes a "short URL" hash of the string passed in parameter. + */ + public static shortHash = function (s: string): string { + let hash = 0; + const strLength = s.length; + if (strLength === 0) { + return ""; + } + for (let i = 0; i < strLength; i++) { + const c = s.charCodeAt(i); + hash = (hash << 5) - hash + c; + hash = hash & hash; // Convert to 32bit integer + } + return Math.abs(hash).toString(36); + }; } diff --git a/front/src/WebRtc/JitsiFactory.ts b/front/src/WebRtc/JitsiFactory.ts index 3d17b5b8..c8824f0e 100644 --- a/front/src/WebRtc/JitsiFactory.ts +++ b/front/src/WebRtc/JitsiFactory.ts @@ -5,6 +5,7 @@ import { get } from "svelte/store"; import CancelablePromise from "cancelable-promise"; import { gameManager } from "../Phaser/Game/GameManager"; import { jitsiParticipantsCountStore, userIsJitsiDominantSpeakerStore } from "../Stores/GameStore"; +import { StringUtils } from "../Utils/StringUtils"; interface jitsiConfigInterface { startWithAudioMuted: boolean; @@ -120,7 +121,7 @@ const slugify = (...args: (string | number)[]): string => { .replace(/[\u0300-\u036f]/g, "") // remove all previously split accents .toLowerCase() .trim() - .replace(/[^a-z0-9 ]/g, "") // remove all chars not letters, numbers and spaces (to be replaced) + .replace(/[^a-z0-9-_ ]/g, "") // remove all chars not letters, numbers, dash, underscores and spaces (to be replaced) .replace(/\s+/g, "-"); // separator }; @@ -135,8 +136,8 @@ class JitsiFactory { /** * Slugifies the room name and prepends the room name with the instance */ - public getRoomName(roomName: string, instance: string): string { - return slugify(instance.replace("/", "-") + "-" + roomName); + public getRoomName(roomName: string, roomId: string): string { + return slugify(StringUtils.shortHash(roomId) + "-" + roomName); } public start( diff --git a/messages/JsonMessages/MapDetailsData.ts b/messages/JsonMessages/MapDetailsData.ts index 503ddbf1..5ee5432c 100644 --- a/messages/JsonMessages/MapDetailsData.ts +++ b/messages/JsonMessages/MapDetailsData.ts @@ -13,7 +13,6 @@ export const isMapDetailsData = z.object({ roomSlug: z.nullable(z.string()), // deprecated contactPage: z.nullable(z.string()), group: z.nullable(z.string()), - instance: z.string(), iframeAuthentication: z.optional(z.nullable(z.string())), // The date (in ISO 8601 format) at which the room will expire diff --git a/pusher/src/Controller/MapController.ts b/pusher/src/Controller/MapController.ts index be910c09..19131a24 100644 --- a/pusher/src/Controller/MapController.ts +++ b/pusher/src/Controller/MapController.ts @@ -76,10 +76,6 @@ export class MapController extends BaseHttpController { * type: string|null * description: The group this room is part of (maps the notion of "world" in WorkAdventure SAAS) * example: myorg/myworld - * instance: - * type: string - * description: The instance of this map. In a public URL: the second part of the URL (_/[instance]/map.json) - * example: global * iframeAuthentication: * type: string|null * description: The URL of the authentication Iframe @@ -115,15 +111,14 @@ export class MapController extends BaseHttpController { if (!ADMIN_API_URL) { const roomUrl = new URL(query.playUri); - const match = /\/_\/([^/]+)\/(.+)/.exec(roomUrl.pathname); + const match = /\/_\/[^/]+\/(.+)/.exec(roomUrl.pathname); if (!match) { res.status(404); res.json({}); return; } - const instance = match[1]; - const mapUrl = roomUrl.protocol + "//" + match[2]; + const mapUrl = roomUrl.protocol + "//" + match[1]; res.json({ mapUrl, @@ -133,7 +128,6 @@ export class MapController extends BaseHttpController { tags: [], contactPage: null, authenticationMandatory: DISABLE_ANONYMOUS, - instance, } as MapDetailsData); return;