diff --git a/src/App.tsx b/src/App.tsx
index d020655d..0822849d 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -23,7 +23,6 @@ import { HomePage } from "./home/HomePage";
import { LoginPage } from "./auth/LoginPage";
import { RegisterPage } from "./auth/RegisterPage";
import { RoomPage } from "./room/RoomPage";
-import { RoomRedirect } from "./room/RoomRedirect";
import { ClientProvider } from "./ClientContext";
import { usePageFocusStyle } from "./usePageFocusStyle";
import { SequenceDiagramViewerPage } from "./SequenceDiagramViewerPage";
@@ -75,7 +74,7 @@ export default function App({ history }: AppProps) {
-
+
diff --git a/src/UrlParams.ts b/src/UrlParams.ts
index eb2b1795..55d8ee10 100644
--- a/src/UrlParams.ts
+++ b/src/UrlParams.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2022 New Vector Ltd
+Copyright 2022 - 2023 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,9 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { useMemo } from "react";
+import { useCallback, useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
+import { Config } from "./config/Config";
+
interface UrlParams {
roomAlias: string | null;
roomId: string | null;
@@ -93,14 +95,38 @@ interface UrlParams {
* @returns The app parameters encoded in the URL
*/
export const getUrlParams = (
- query: string = window.location.search,
- fragment: string = window.location.hash
+ ignoreRoomAlias?: boolean,
+ location: Location = window.location
): UrlParams => {
- const fragmentQueryStart = fragment.indexOf("?");
+ const { href, origin, search, hash } = location;
+
+ let roomAlias: string | undefined;
+ if (!ignoreRoomAlias) {
+ roomAlias = href.substring(origin.length + 1);
+
+ // If we have none, we throw
+ if (!roomAlias) {
+ throw Error("No pathname");
+ }
+ // Delete "/room/" and "?", if present
+ if (roomAlias.startsWith("room/")) {
+ roomAlias = roomAlias.substring("room/".length).split("?")[0];
+ }
+ // Add "#", if not present
+ if (!roomAlias.includes("#")) {
+ roomAlias = `#${roomAlias}`;
+ }
+ // Add server part, if missing
+ if (!roomAlias.includes(":")) {
+ roomAlias = `${roomAlias}:${Config.defaultServerName()}`;
+ }
+ }
+
+ const fragmentQueryStart = hash.indexOf("?");
const fragmentParams = new URLSearchParams(
- fragmentQueryStart === -1 ? "" : fragment.substring(fragmentQueryStart)
+ fragmentQueryStart === -1 ? "" : hash.substring(fragmentQueryStart)
);
- const queryParams = new URLSearchParams(query);
+ const queryParams = new URLSearchParams(search);
// Normally, URL params should be encoded in the fragment so as to avoid
// leaking them to the server. However, we also check the normal query
@@ -114,16 +140,10 @@ export const getUrlParams = (
...queryParams.getAll(name),
];
- // The part of the fragment before the ?
- const fragmentRoute =
- fragmentQueryStart === -1
- ? fragment
- : fragment.substring(0, fragmentQueryStart);
-
const fontScale = parseFloat(getParam("fontScale") ?? "");
return {
- roomAlias: fragmentRoute.length > 1 ? fragmentRoute : null,
+ roomAlias: !roomAlias || roomAlias.includes("!") ? null : roomAlias,
roomId: getParam("roomId"),
viaServers: getAllParams("via"),
isEmbedded: hasParam("embed"),
@@ -149,6 +169,18 @@ export const getUrlParams = (
* @returns The app parameters for the current URL
*/
export const useUrlParams = (): UrlParams => {
- const { hash, search } = useLocation();
- return useMemo(() => getUrlParams(search, hash), [search, hash]);
+ const getParams = useCallback(() => {
+ return getUrlParams(false, window.location);
+ }, []);
+
+ const reactDomLocation = useLocation();
+ const [urlParams, setUrlParams] = useState(getParams());
+
+ useEffect(() => {
+ if (window.location !== reactDomLocation) {
+ setUrlParams(getParams());
+ }
+ }, [getParams, reactDomLocation]);
+
+ return urlParams;
};
diff --git a/src/initializer.tsx b/src/initializer.tsx
index 37e659e7..f8a6c985 100644
--- a/src/initializer.tsx
+++ b/src/initializer.tsx
@@ -55,7 +55,7 @@ export class Initializer {
languageDetector.addDetector({
name: "urlFragment",
// Look for a language code in the URL's fragment
- lookup: () => getUrlParams().lang ?? undefined,
+ lookup: () => getUrlParams(true).lang ?? undefined,
});
i18n
@@ -136,7 +136,7 @@ export class Initializer {
}
// Custom fonts
- const { fonts, fontScale } = getUrlParams();
+ const { fonts, fontScale } = getUrlParams(true);
if (fontScale !== null) {
document.documentElement.style.setProperty(
"--font-scale",
diff --git a/src/room/RoomRedirect.tsx b/src/room/RoomRedirect.tsx
deleted file mode 100644
index 3d45086d..00000000
--- a/src/room/RoomRedirect.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
-Copyright 2022 New Vector Ltd
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import { useEffect } from "react";
-import { useLocation, useHistory } from "react-router-dom";
-
-import { Config } from "../config/Config";
-import { LoadingView } from "../FullScreenView";
-
-// A component that, when loaded, redirects the client to a full room URL
-// based on the current URL being an abbreviated room URL
-export function RoomRedirect() {
- const { pathname } = useLocation();
- const history = useHistory();
-
- useEffect(() => {
- let roomId = pathname;
-
- if (pathname.startsWith("/")) {
- roomId = roomId.substring(1, roomId.length);
- }
-
- if (!roomId.startsWith("#") && !roomId.startsWith("!")) {
- roomId = `#${roomId}:${Config.defaultServerName()}`;
- }
-
- history.replace(`/room/${roomId.toLowerCase()}`);
- }, [pathname, history]);
-
- return ;
-}
diff --git a/test/UrlParams-test.ts b/test/UrlParams-test.ts
new file mode 100644
index 00000000..9f691326
--- /dev/null
+++ b/test/UrlParams-test.ts
@@ -0,0 +1,146 @@
+/*
+Copyright 2023 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { mocked } from "jest-mock";
+import { getUrlParams } from "../src/UrlParams";
+import { Config } from "../src/config/Config";
+
+const ROOM_NAME = "roomNameHere";
+const ROOM_ID = "d45f138fsd";
+const ORIGIN = "https://call.element.io";
+const HOMESERVER = "call.ems.host";
+
+jest.mock("../src/config/Config");
+
+describe("UrlParams", () => {
+ beforeAll(() => {
+ mocked(Config.defaultServerName).mockReturnValue("call.ems.host");
+ });
+
+ describe("handles URL with /room/", () => {
+ it("and nothing else", () => {
+ expect(
+ getUrlParams(false, {
+ origin: ORIGIN,
+ href: `${ORIGIN}/room/${ROOM_NAME}`,
+ search: "",
+ hash: "",
+ } as Location).roomAlias
+ ).toBe(`#${ROOM_NAME}:${HOMESERVER}`);
+ });
+
+ it("and #", () => {
+ expect(
+ getUrlParams(false, {
+ origin: ORIGIN,
+ href: `${ORIGIN}/room/#${ROOM_NAME}`,
+ search: "",
+ hash: "",
+ } as Location).roomAlias
+ ).toBe(`#${ROOM_NAME}:${HOMESERVER}`);
+ });
+
+ it("and # and server part", () => {
+ expect(
+ getUrlParams(false, {
+ origin: ORIGIN,
+ href: `${ORIGIN}/room/#${ROOM_NAME}:${HOMESERVER}`,
+ search: "",
+ hash: "",
+ } as Location).roomAlias
+ ).toBe(`#${ROOM_NAME}:${HOMESERVER}`);
+ });
+
+ it("and server part", () => {
+ expect(
+ getUrlParams(false, {
+ origin: ORIGIN,
+ href: `${ORIGIN}/room/${ROOM_NAME}:${HOMESERVER}`,
+ search: "",
+ hash: "",
+ } as Location).roomAlias
+ ).toBe(`#${ROOM_NAME}:${HOMESERVER}`);
+ });
+ });
+
+ describe("handles URL without /room/", () => {
+ it("and nothing else", () => {
+ expect(
+ getUrlParams(false, {
+ origin: ORIGIN,
+ href: `${ORIGIN}/${ROOM_NAME}`,
+ search: "",
+ hash: "",
+ } as Location).roomAlias
+ ).toBe(`#${ROOM_NAME}:${HOMESERVER}`);
+ });
+
+ it("and with #", () => {
+ expect(
+ getUrlParams(false, {
+ origin: ORIGIN,
+ href: `${ORIGIN}/room/#${ROOM_NAME}`,
+ search: "",
+ hash: "",
+ } as Location).roomAlias
+ ).toBe(`#${ROOM_NAME}:${HOMESERVER}`);
+ });
+
+ it("and with # and server part", () => {
+ expect(
+ getUrlParams(false, {
+ origin: ORIGIN,
+ href: `${ORIGIN}/room/#${ROOM_NAME}:${HOMESERVER}`,
+ search: "",
+ hash: "",
+ } as Location).roomAlias
+ ).toBe(`#${ROOM_NAME}:${HOMESERVER}`);
+ });
+
+ it("and with server part", () => {
+ expect(
+ getUrlParams(false, {
+ origin: ORIGIN,
+ href: `${ORIGIN}/room/${ROOM_NAME}:${HOMESERVER}`,
+ search: "",
+ hash: "",
+ } as Location).roomAlias
+ ).toBe(`#${ROOM_NAME}:${HOMESERVER}`);
+ });
+ });
+
+ describe("handles search params", () => {
+ it("(roomId)", () => {
+ expect(
+ getUrlParams(true, {
+ search: `?roomId=${ROOM_ID}`,
+ hash: "",
+ } as Location).roomId
+ ).toBe(ROOM_ID);
+ });
+ });
+
+ it("ignores room alias", () => {
+ expect(
+ getUrlParams(true, {
+ origin: ORIGIN,
+ href: `${ORIGIN}/room/${ROOM_NAME}:${HOMESERVER}`,
+ hash: "",
+ search: "",
+ } as Location).roomAlias
+ ).toBeFalsy();
+ });
+});