Move default homeserver to config file

This commit is contained in:
David Baker 2022-12-20 17:26:45 +00:00
parent 282a4853cf
commit 96de515e56
13 changed files with 69 additions and 54 deletions

View file

@ -32,7 +32,6 @@ import { useTranslation } from "react-i18next";
import { ErrorView } from "./FullScreenView"; import { ErrorView } from "./FullScreenView";
import { import {
initClient, initClient,
defaultHomeserver,
CryptoStoreIntegrityError, CryptoStoreIntegrityError,
fallbackICEServerAllowed, fallbackICEServerAllowed,
} from "./matrix-utils"; } from "./matrix-utils";
@ -40,6 +39,7 @@ import { widget } from "./widget";
import { PosthogAnalytics, RegistrationType } from "./PosthogAnalytics"; import { PosthogAnalytics, RegistrationType } from "./PosthogAnalytics";
import { translatedError } from "./TranslatedError"; import { translatedError } from "./TranslatedError";
import { useEventTarget } from "./useEvents"; import { useEventTarget } from "./useEvents";
import { Config } from "./config/Config";
declare global { declare global {
interface Window { interface Window {
@ -139,7 +139,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
return { return {
client: await initClient( client: await initClient(
{ {
baseUrl: defaultHomeserver, baseUrl: Config.defaultHomeserverUrl(),
accessToken: access_token, accessToken: access_token,
userId: user_id, userId: user_id,
deviceId: device_id, deviceId: device_id,
@ -155,7 +155,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
try { try {
const client = await initClient( const client = await initClient(
{ {
baseUrl: defaultHomeserver, baseUrl: Config.defaultHomeserverUrl(),
accessToken: access_token, accessToken: access_token,
userId: user_id, userId: user_id,
deviceId: device_id, deviceId: device_id,

View file

@ -29,11 +29,11 @@ import { ReactComponent as Logo } from "../icons/LogoLarge.svg";
import { useClient } from "../ClientContext"; import { useClient } from "../ClientContext";
import { FieldRow, InputField, ErrorMessage } from "../input/Input"; import { FieldRow, InputField, ErrorMessage } from "../input/Input";
import { Button } from "../button"; import { Button } from "../button";
import { defaultHomeserver, defaultHomeserverHost } from "../matrix-utils";
import styles from "./LoginPage.module.css"; import styles from "./LoginPage.module.css";
import { useInteractiveLogin } from "./useInteractiveLogin"; import { useInteractiveLogin } from "./useInteractiveLogin";
import { usePageTitle } from "../usePageTitle"; import { usePageTitle } from "../usePageTitle";
import { PosthogAnalytics } from "../PosthogAnalytics"; import { PosthogAnalytics } from "../PosthogAnalytics";
import { Config } from "../config/Config";
export const LoginPage: FC = () => { export const LoginPage: FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -41,7 +41,7 @@ export const LoginPage: FC = () => {
const { setClient } = useClient(); const { setClient } = useClient();
const login = useInteractiveLogin(); const login = useInteractiveLogin();
const homeserver = defaultHomeserver; // TODO: Make this configurable const homeserver = Config.defaultHomeserverUrl(); // TODO: Make this configurable
const usernameRef = useRef<HTMLInputElement>(); const usernameRef = useRef<HTMLInputElement>();
const passwordRef = useRef<HTMLInputElement>(); const passwordRef = useRef<HTMLInputElement>();
const history = useHistory(); const history = useHistory();
@ -76,11 +76,9 @@ export const LoginPage: FC = () => {
); );
const homeserverHost = useMemo(() => { const homeserverHost = useMemo(() => {
try { // XXX: This isn't really correct: the server name of an HS may not
// be the same as the hostname of the client API endpoint.
return new URL(homeserver).host; return new URL(homeserver).host;
} catch (error) {
return defaultHomeserverHost;
}
}, [homeserver]); }, [homeserver]);
return ( return (

View file

@ -31,7 +31,6 @@ import { Trans, useTranslation } from "react-i18next";
import { FieldRow, InputField, ErrorMessage } from "../input/Input"; import { FieldRow, InputField, ErrorMessage } from "../input/Input";
import { Button } from "../button"; import { Button } from "../button";
import { useClient } from "../ClientContext"; import { useClient } from "../ClientContext";
import { defaultHomeserverHost } from "../matrix-utils";
import { useInteractiveRegistration } from "./useInteractiveRegistration"; import { useInteractiveRegistration } from "./useInteractiveRegistration";
import styles from "./LoginPage.module.css"; import styles from "./LoginPage.module.css";
import { ReactComponent as Logo } from "../icons/LogoLarge.svg"; import { ReactComponent as Logo } from "../icons/LogoLarge.svg";
@ -40,6 +39,7 @@ import { useRecaptcha } from "./useRecaptcha";
import { Caption, Link } from "../typography/Typography"; import { Caption, Link } from "../typography/Typography";
import { usePageTitle } from "../usePageTitle"; import { usePageTitle } from "../usePageTitle";
import { PosthogAnalytics } from "../PosthogAnalytics"; import { PosthogAnalytics } from "../PosthogAnalytics";
import { Config } from "../config/Config";
export const RegisterPage: FC = () => { export const RegisterPage: FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -165,7 +165,7 @@ export const RegisterPage: FC = () => {
autoCorrect="off" autoCorrect="off"
autoCapitalize="none" autoCapitalize="none"
prefix="@" prefix="@"
suffix={`:${defaultHomeserverHost}`} suffix={`:${Config.defaultServerName()}`}
/> />
</FieldRow> </FieldRow>
<FieldRow> <FieldRow>

View file

@ -18,7 +18,7 @@ import { useCallback } from "react";
import { InteractiveAuth } from "matrix-js-sdk/src/interactive-auth"; import { InteractiveAuth } from "matrix-js-sdk/src/interactive-auth";
import { createClient, MatrixClient } from "matrix-js-sdk/src/matrix"; import { createClient, MatrixClient } from "matrix-js-sdk/src/matrix";
import { initClient, defaultHomeserver } from "../matrix-utils"; import { initClient } from "../matrix-utils";
import { Session } from "../ClientContext"; import { Session } from "../ClientContext";
export const useInteractiveLogin = () => export const useInteractiveLogin = () =>
@ -59,7 +59,7 @@ export const useInteractiveLogin = () =>
const client = await initClient( const client = await initClient(
{ {
baseUrl: defaultHomeserver, baseUrl: homeserver,
accessToken: access_token, accessToken: access_token,
userId: user_id, userId: user_id,
deviceId: device_id, deviceId: device_id,

View file

@ -18,8 +18,9 @@ import { useState, useEffect, useCallback, useRef } from "react";
import { InteractiveAuth } from "matrix-js-sdk/src/interactive-auth"; import { InteractiveAuth } from "matrix-js-sdk/src/interactive-auth";
import { createClient, MatrixClient } from "matrix-js-sdk/src/matrix"; import { createClient, MatrixClient } from "matrix-js-sdk/src/matrix";
import { initClient, defaultHomeserver } from "../matrix-utils"; import { initClient } from "../matrix-utils";
import { Session } from "../ClientContext"; import { Session } from "../ClientContext";
import { Config } from "../config/Config";
export const useInteractiveRegistration = (): [ export const useInteractiveRegistration = (): [
string, string,
@ -37,7 +38,9 @@ export const useInteractiveRegistration = (): [
const authClient = useRef<MatrixClient>(); const authClient = useRef<MatrixClient>();
if (!authClient.current) { if (!authClient.current) {
authClient.current = createClient({ baseUrl: defaultHomeserver }); authClient.current = createClient({
baseUrl: Config.defaultHomeserverUrl(),
});
} }
useEffect(() => { useEffect(() => {
@ -92,7 +95,7 @@ export const useInteractiveRegistration = (): [
const client = await initClient( const client = await initClient(
{ {
baseUrl: defaultHomeserver, baseUrl: Config.defaultHomeserverUrl(),
accessToken: access_token, accessToken: access_token,
userId: user_id, userId: user_id,
deviceId: device_id, deviceId: device_id,

View file

@ -14,11 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { import { DEFAULT_CONFIG, ConfigOptions } from "./ConfigOptions";
DEFAULT_CONFIG,
ConfigOptions,
ResolvedConfigOptions,
} from "./ConfigOptions";
export class Config { export class Config {
private static internalInstance: Config; private static internalInstance: Config;
@ -41,7 +37,26 @@ export class Config {
return Config.internalInstance.initPromise; return Config.internalInstance.initPromise;
} }
public config: ResolvedConfigOptions; // Convenience accessors
public static defaultHomeserverUrl(): string | undefined {
const defaultServerConfig = Config.instance.config.default_server_config;
if (!defaultServerConfig) {
return undefined;
}
return defaultServerConfig["m.homeserver"]?.base_url;
}
public static defaultServerName(): string | undefined {
const defaultServerConfig = Config.instance.config.default_server_config;
if (!defaultServerConfig) {
return undefined;
}
return defaultServerConfig["m.homeserver"]?.server_name;
}
public config: ConfigOptions;
private initPromise: Promise<void>; private initPromise: Promise<void>;
} }
@ -59,7 +74,7 @@ async function downloadConfig(
// Lack of a config isn't an error, we should just use the defaults. // Lack of a config isn't an error, we should just use the defaults.
// Also treat a blank config as no config, assuming the status code is 0, because we don't get 404s from file: // Also treat a blank config as no config, assuming the status code is 0, because we don't get 404s from file:
// URIs so this is the only way we can not fail if the file doesn't exist when loading from a file:// URI. // URIs so this is the only way we can not fail if the file doesn't exist when loading from a file:// URI.
return {}; return DEFAULT_CONFIG;
} }
return res.json(); return res.json();

View file

@ -19,21 +19,24 @@ export interface ConfigOptions {
rageshake?: { rageshake?: {
submit_url: string; submit_url: string;
}; };
}
export interface ResolvedConfigOptions extends ConfigOptions { // Describes the default homeserver to use. The same format as Element Web
sentry: { // (without identity servers as we don't use them).
DSN: string; default_server_config: {
environment: string; ["m.homeserver"]: {
base_url: string;
server_name: string;
}; };
rageshake: {
submit_url: string;
}; };
} }
export const DEFAULT_CONFIG: ResolvedConfigOptions = { export const DEFAULT_CONFIG: ConfigOptions = {
sentry: { DSN: "", environment: "production" }, default_server_config: {
rageshake: { ["m.homeserver"]: {
submit_url: "https://element.io/bugreports/submit", // These are probably poor guesses - we may want to just not work without
// a config file.
base_url: `${window.location.protocol}//${window.location.host}`,
server_name: window.location.host,
},
}, },
}; };

View file

@ -23,7 +23,6 @@ import * as Sentry from "@sentry/react";
import { getUrlParams } from "./UrlParams"; import { getUrlParams } from "./UrlParams";
import { Config } from "./config/Config"; import { Config } from "./config/Config";
import { DEFAULT_CONFIG } from "./config/ConfigOptions";
enum LoadState { enum LoadState {
None, None,
@ -193,10 +192,8 @@ export class Initializer {
this.loadStates.config === LoadState.Loaded this.loadStates.config === LoadState.Loaded
) { ) {
Sentry.init({ Sentry.init({
dsn: Config.instance.config.sentry?.DSN ?? DEFAULT_CONFIG.sentry.DSN, dsn: Config.instance.config.sentry?.DSN,
environment: environment: Config.instance.config.sentry?.environment,
Config.instance.config.sentry.environment ??
DEFAULT_CONFIG.sentry.environment,
integrations: [ integrations: [
new Integrations.BrowserTracing({ new Integrations.BrowserTracing({
routingInstrumentation: routingInstrumentation:

View file

@ -19,15 +19,11 @@ import type { Room } from "matrix-js-sdk/src/models/room";
import IndexedDBWorker from "./IndexedDBWorker?worker"; import IndexedDBWorker from "./IndexedDBWorker?worker";
import { getUrlParams } from "./UrlParams"; import { getUrlParams } from "./UrlParams";
import { loadOlm } from "./olm"; import { loadOlm } from "./olm";
import { Config } from "./config/Config";
export const defaultHomeserver =
(import.meta.env.VITE_DEFAULT_HOMESERVER as string) ??
`${window.location.protocol}//${window.location.host}`;
export const fallbackICEServerAllowed = export const fallbackICEServerAllowed =
import.meta.env.VITE_FALLBACK_STUN_ALLOWED === "true"; import.meta.env.VITE_FALLBACK_STUN_ALLOWED === "true";
export const defaultHomeserverHost = new URL(defaultHomeserver).host;
export class CryptoStoreIntegrityError extends Error { export class CryptoStoreIntegrityError extends Error {
constructor() { constructor() {
super("Crypto store data was expected, but none was found"); super("Crypto store data was expected, but none was found");
@ -206,7 +202,7 @@ export function roomNameFromRoomId(roomId: string): string {
.toLowerCase(); .toLowerCase();
} }
export function isLocalRoomId(roomId: string): boolean { export function isLocalRoomId(roomId: string, client: MatrixClient): boolean {
if (!roomId) { if (!roomId) {
return false; return false;
} }
@ -217,7 +213,7 @@ export function isLocalRoomId(roomId: string): boolean {
return false; return false;
} }
return parts[1] === defaultHomeserverHost; return parts[1] === client.getDomain();
} }
export async function createRoom( export async function createRoom(
@ -291,11 +287,12 @@ export async function createRoom(
return [fullAliasFromRoomName(name, client), result.room_id]; return [fullAliasFromRoomName(name, client), result.room_id];
} }
// Returns a URL to that will load Element Call with the given room
export function getRoomUrl(roomIdOrAlias: string): string { export function getRoomUrl(roomIdOrAlias: string): string {
if (roomIdOrAlias.startsWith("#")) { if (roomIdOrAlias.startsWith("#")) {
const [localPart, host] = roomIdOrAlias.replace("#", "").split(":"); const [localPart, host] = roomIdOrAlias.replace("#", "").split(":");
if (host !== defaultHomeserverHost) { if (host !== Config.defaultServerName()) {
return `${window.location.protocol}//${window.location.host}/room/${roomIdOrAlias}`; return `${window.location.protocol}//${window.location.host}/room/${roomIdOrAlias}`;
} else { } else {
return `${window.location.protocol}//${window.location.host}/${localPart}`; return `${window.location.protocol}//${window.location.host}/${localPart}`;

View file

@ -17,9 +17,11 @@ limitations under the License.
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useLocation, useHistory } from "react-router-dom"; import { useLocation, useHistory } from "react-router-dom";
import { defaultHomeserverHost } from "../matrix-utils"; import { Config } from "../config/Config";
import { LoadingView } from "../FullScreenView"; 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() { export function RoomRedirect() {
const { pathname } = useLocation(); const { pathname } = useLocation();
const history = useHistory(); const history = useHistory();
@ -32,7 +34,7 @@ export function RoomRedirect() {
} }
if (!roomId.startsWith("#") && !roomId.startsWith("!")) { if (!roomId.startsWith("#") && !roomId.startsWith("!")) {
roomId = `#${roomId}:${defaultHomeserverHost}`; roomId = `#${roomId}:${Config.defaultServerName()}`;
} }
history.replace(`/room/${roomId.toLowerCase()}`); history.replace(`/room/${roomId.toLowerCase()}`);

View file

@ -61,7 +61,7 @@ export const useLoadGroupCall = (
return room; return room;
} catch (error) { } catch (error) {
if ( if (
isLocalRoomId(roomIdOrAlias) && isLocalRoomId(roomIdOrAlias, client) &&
(error.errcode === "M_NOT_FOUND" || (error.errcode === "M_NOT_FOUND" ||
(error.message && (error.message &&
error.message.indexOf("Failed to fetch alias") !== -1)) error.message.indexOf("Failed to fetch alias") !== -1))

View file

@ -18,7 +18,7 @@ import { useEffect } from "react";
export function usePageTitle(title: string): void { export function usePageTitle(title: string): void {
useEffect(() => { useEffect(() => {
const productName = import.meta.env.VITE_PRODUCT_NAME || "Element Call"; const productName = "Element Call";
document.title = title ? `${productName} | ${title}` : productName; document.title = title ? `${productName} | ${title}` : productName;
}, [title]); }, [title]);
} }

View file

@ -31,7 +31,7 @@ export default defineConfig(({ mode }) => {
svgrPlugin(), svgrPlugin(),
htmlTemplate.default({ htmlTemplate.default({
data: { data: {
title: env.VITE_PRODUCT_NAME || "Element Call", title: "Element Call",
}, },
}), }),
], ],