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.
This commit is contained in:
David Négrier 2022-04-15 21:12:00 +02:00
parent 184aeb354a
commit b89997c9f1
8 changed files with 26 additions and 28 deletions

View File

@ -562,14 +562,13 @@ export class GameRoom {
if (!ADMIN_API_URL) { if (!ADMIN_API_URL) {
const roomUrlObj = new URL(roomUrl); const roomUrlObj = new URL(roomUrl);
const match = /\/_\/([^/]+)\/(.+)/.exec(roomUrlObj.pathname); const match = /\/_\/[^/]+\/(.+)/.exec(roomUrlObj.pathname);
if (!match) { if (!match) {
console.error("Unexpected room URL", roomUrl); console.error("Unexpected room URL", roomUrl);
throw new Error('Unexpected room URL "' + roomUrl + '"'); throw new Error('Unexpected room URL "' + roomUrl + '"');
} }
const instance = match[1]; const mapUrl = roomUrlObj.protocol + "//" + match[1];
const mapUrl = roomUrlObj.protocol + "//" + match[2];
return { return {
mapUrl, mapUrl,
@ -579,7 +578,6 @@ export class GameRoom {
roomSlug: null, roomSlug: null,
contactPage: null, contactPage: null,
group: null, group: null,
instance,
}; };
} }

View File

@ -18,7 +18,6 @@ export class Room {
private _authenticationMandatory: boolean = DISABLE_ANONYMOUS; private _authenticationMandatory: boolean = DISABLE_ANONYMOUS;
private _iframeAuthentication?: string = OPID_LOGIN_SCREEN_PROVIDER; private _iframeAuthentication?: string = OPID_LOGIN_SCREEN_PROVIDER;
private _mapUrl: string | undefined; private _mapUrl: string | undefined;
private _instance: string | undefined;
private readonly _search: URLSearchParams; private readonly _search: URLSearchParams;
private _contactPage: string | undefined; private _contactPage: string | undefined;
private _group: string | null = null; private _group: string | null = null;
@ -121,7 +120,6 @@ export class Room {
this._canReport = data.canReport ?? false; this._canReport = data.canReport ?? false;
this._loadingLogo = data.loadingLogo ?? undefined; this._loadingLogo = data.loadingLogo ?? undefined;
this._loginSceneLogo = data.loginSceneLogo ?? undefined; this._loginSceneLogo = data.loginSceneLogo ?? undefined;
this._instance = data.instance;
return new MapDetail(data.mapUrl); return new MapDetail(data.mapUrl);
} else { } else {
console.log(data); console.log(data);
@ -200,13 +198,6 @@ export class Room {
return this._group; return this._group;
} }
get instance(): string {
if (!this._instance) {
throw new Error("Instance not fetched yet");
}
return this._instance;
}
get expireOn(): Date | undefined { get expireOn(): Date | undefined {
return this._expireOn; return this._expireOn;
} }

View File

@ -67,7 +67,7 @@ export class GameMapPropertiesListener {
}); });
} else { } else {
const openJitsiRoomFunction = () => { 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; const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined;
if (JITSI_PRIVATE_MODE && !jitsiUrl) { if (JITSI_PRIVATE_MODE && !jitsiUrl) {

View File

@ -185,7 +185,6 @@ export class GameScene extends DirtyScene {
private biggestAvailableAreaStoreUnsubscribe!: () => void; private biggestAvailableAreaStoreUnsubscribe!: () => void;
MapUrlFile: string; MapUrlFile: string;
roomUrl: string; roomUrl: string;
instance: string;
currentTick!: number; currentTick!: number;
lastSentTick!: number; // The last tick at which a position was sent. lastSentTick!: number; // The last tick at which a position was sent.
@ -234,7 +233,6 @@ export class GameScene extends DirtyScene {
}); });
this.Terrains = []; this.Terrains = [];
this.groups = new Map<number, Sprite>(); this.groups = new Map<number, Sprite>();
this.instance = room.instance;
this.MapUrlFile = MapUrlFile; this.MapUrlFile = MapUrlFile;
this.roomUrl = room.key; this.roomUrl = room.key;

View File

@ -9,4 +9,21 @@ export class StringUtils {
} }
return { x: values[0], y: values[1] }; 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);
};
} }

View File

@ -5,6 +5,7 @@ import { get } from "svelte/store";
import CancelablePromise from "cancelable-promise"; import CancelablePromise from "cancelable-promise";
import { gameManager } from "../Phaser/Game/GameManager"; import { gameManager } from "../Phaser/Game/GameManager";
import { jitsiParticipantsCountStore, userIsJitsiDominantSpeakerStore } from "../Stores/GameStore"; import { jitsiParticipantsCountStore, userIsJitsiDominantSpeakerStore } from "../Stores/GameStore";
import { StringUtils } from "../Utils/StringUtils";
interface jitsiConfigInterface { interface jitsiConfigInterface {
startWithAudioMuted: boolean; startWithAudioMuted: boolean;
@ -120,7 +121,7 @@ const slugify = (...args: (string | number)[]): string => {
.replace(/[\u0300-\u036f]/g, "") // remove all previously split accents .replace(/[\u0300-\u036f]/g, "") // remove all previously split accents
.toLowerCase() .toLowerCase()
.trim() .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 .replace(/\s+/g, "-"); // separator
}; };
@ -135,8 +136,8 @@ class JitsiFactory {
/** /**
* Slugifies the room name and prepends the room name with the instance * Slugifies the room name and prepends the room name with the instance
*/ */
public getRoomName(roomName: string, instance: string): string { public getRoomName(roomName: string, roomId: string): string {
return slugify(instance.replace("/", "-") + "-" + roomName); return slugify(StringUtils.shortHash(roomId) + "-" + roomName);
} }
public start( public start(

View File

@ -13,7 +13,6 @@ export const isMapDetailsData = z.object({
roomSlug: z.nullable(z.string()), // deprecated roomSlug: z.nullable(z.string()), // deprecated
contactPage: z.nullable(z.string()), contactPage: z.nullable(z.string()),
group: z.nullable(z.string()), group: z.nullable(z.string()),
instance: z.string(),
iframeAuthentication: z.optional(z.nullable(z.string())), iframeAuthentication: z.optional(z.nullable(z.string())),
// The date (in ISO 8601 format) at which the room will expire // The date (in ISO 8601 format) at which the room will expire

View File

@ -76,10 +76,6 @@ export class MapController extends BaseHttpController {
* type: string|null * type: string|null
* description: The group this room is part of (maps the notion of "world" in WorkAdventure SAAS) * description: The group this room is part of (maps the notion of "world" in WorkAdventure SAAS)
* example: myorg/myworld * 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: * iframeAuthentication:
* type: string|null * type: string|null
* description: The URL of the authentication Iframe * description: The URL of the authentication Iframe
@ -115,15 +111,14 @@ export class MapController extends BaseHttpController {
if (!ADMIN_API_URL) { if (!ADMIN_API_URL) {
const roomUrl = new URL(query.playUri); const roomUrl = new URL(query.playUri);
const match = /\/_\/([^/]+)\/(.+)/.exec(roomUrl.pathname); const match = /\/_\/[^/]+\/(.+)/.exec(roomUrl.pathname);
if (!match) { if (!match) {
res.status(404); res.status(404);
res.json({}); res.json({});
return; return;
} }
const instance = match[1]; const mapUrl = roomUrl.protocol + "//" + match[1];
const mapUrl = roomUrl.protocol + "//" + match[2];
res.json({ res.json({
mapUrl, mapUrl,
@ -133,7 +128,6 @@ export class MapController extends BaseHttpController {
tags: [], tags: [],
contactPage: null, contactPage: null,
authenticationMandatory: DISABLE_ANONYMOUS, authenticationMandatory: DISABLE_ANONYMOUS,
instance,
} as MapDetailsData); } as MapDetailsData);
return; return;