@@ -23,7 +23,6 @@ import { HomePage } from "./home/HomePage";
|
|||||||
import { LoginPage } from "./auth/LoginPage";
|
import { LoginPage } from "./auth/LoginPage";
|
||||||
import { RegisterPage } from "./auth/RegisterPage";
|
import { RegisterPage } from "./auth/RegisterPage";
|
||||||
import { RoomPage } from "./room/RoomPage";
|
import { RoomPage } from "./room/RoomPage";
|
||||||
import { RoomRedirect } from "./room/RoomRedirect";
|
|
||||||
import { ClientProvider } from "./ClientContext";
|
import { ClientProvider } from "./ClientContext";
|
||||||
import { usePageFocusStyle } from "./usePageFocusStyle";
|
import { usePageFocusStyle } from "./usePageFocusStyle";
|
||||||
import { SequenceDiagramViewerPage } from "./SequenceDiagramViewerPage";
|
import { SequenceDiagramViewerPage } from "./SequenceDiagramViewerPage";
|
||||||
@@ -75,7 +74,7 @@ export default function App({ history }: AppProps) {
|
|||||||
<SequenceDiagramViewerPage />
|
<SequenceDiagramViewerPage />
|
||||||
</SentryRoute>
|
</SentryRoute>
|
||||||
<SentryRoute path="*">
|
<SentryRoute path="*">
|
||||||
<RoomRedirect />
|
<RoomPage />
|
||||||
</SentryRoute>
|
</SentryRoute>
|
||||||
</Switch>
|
</Switch>
|
||||||
</OverlayProvider>
|
</OverlayProvider>
|
||||||
|
|||||||
@@ -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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useMemo } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
|
import { Config } from "./config/Config";
|
||||||
|
|
||||||
interface UrlParams {
|
interface UrlParams {
|
||||||
roomAlias: string | null;
|
roomAlias: string | null;
|
||||||
roomId: string | null;
|
roomId: string | null;
|
||||||
@@ -93,14 +95,38 @@ interface UrlParams {
|
|||||||
* @returns The app parameters encoded in the URL
|
* @returns The app parameters encoded in the URL
|
||||||
*/
|
*/
|
||||||
export const getUrlParams = (
|
export const getUrlParams = (
|
||||||
query: string = window.location.search,
|
ignoreRoomAlias?: boolean,
|
||||||
fragment: string = window.location.hash
|
location: Location = window.location
|
||||||
): UrlParams => {
|
): 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(
|
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
|
// 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
|
// leaking them to the server. However, we also check the normal query
|
||||||
@@ -114,16 +140,10 @@ export const getUrlParams = (
|
|||||||
...queryParams.getAll(name),
|
...queryParams.getAll(name),
|
||||||
];
|
];
|
||||||
|
|
||||||
// The part of the fragment before the ?
|
|
||||||
const fragmentRoute =
|
|
||||||
fragmentQueryStart === -1
|
|
||||||
? fragment
|
|
||||||
: fragment.substring(0, fragmentQueryStart);
|
|
||||||
|
|
||||||
const fontScale = parseFloat(getParam("fontScale") ?? "");
|
const fontScale = parseFloat(getParam("fontScale") ?? "");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
roomAlias: fragmentRoute.length > 1 ? fragmentRoute : null,
|
roomAlias: !roomAlias || roomAlias.includes("!") ? null : roomAlias,
|
||||||
roomId: getParam("roomId"),
|
roomId: getParam("roomId"),
|
||||||
viaServers: getAllParams("via"),
|
viaServers: getAllParams("via"),
|
||||||
isEmbedded: hasParam("embed"),
|
isEmbedded: hasParam("embed"),
|
||||||
@@ -149,6 +169,18 @@ export const getUrlParams = (
|
|||||||
* @returns The app parameters for the current URL
|
* @returns The app parameters for the current URL
|
||||||
*/
|
*/
|
||||||
export const useUrlParams = (): UrlParams => {
|
export const useUrlParams = (): UrlParams => {
|
||||||
const { hash, search } = useLocation();
|
const getParams = useCallback(() => {
|
||||||
return useMemo(() => getUrlParams(search, hash), [search, hash]);
|
return getUrlParams(false, window.location);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const reactDomLocation = useLocation();
|
||||||
|
const [urlParams, setUrlParams] = useState(getParams());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (window.location !== reactDomLocation) {
|
||||||
|
setUrlParams(getParams());
|
||||||
|
}
|
||||||
|
}, [getParams, reactDomLocation]);
|
||||||
|
|
||||||
|
return urlParams;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export class Initializer {
|
|||||||
languageDetector.addDetector({
|
languageDetector.addDetector({
|
||||||
name: "urlFragment",
|
name: "urlFragment",
|
||||||
// Look for a language code in the URL's fragment
|
// Look for a language code in the URL's fragment
|
||||||
lookup: () => getUrlParams().lang ?? undefined,
|
lookup: () => getUrlParams(true).lang ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
i18n
|
i18n
|
||||||
@@ -136,7 +136,7 @@ export class Initializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Custom fonts
|
// Custom fonts
|
||||||
const { fonts, fontScale } = getUrlParams();
|
const { fonts, fontScale } = getUrlParams(true);
|
||||||
if (fontScale !== null) {
|
if (fontScale !== null) {
|
||||||
document.documentElement.style.setProperty(
|
document.documentElement.style.setProperty(
|
||||||
"--font-scale",
|
"--font-scale",
|
||||||
|
|||||||
@@ -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 <LoadingView />;
|
|
||||||
}
|
|
||||||
146
test/UrlParams-test.ts
Normal file
146
test/UrlParams-test.ts
Normal file
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user