Merge pull request #798 from vector-im/dbkr/move_to_config_file

Move default homeserver to config file
This commit is contained in:
David Baker 2022-12-21 17:57:16 +00:00 committed by GitHub
commit 51be754ad8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 97 additions and 83 deletions

View file

@ -3,8 +3,6 @@ on:
pull_request: {} pull_request: {}
push: push:
branches: [main] branches: [main]
env:
VITE_DEFAULT_HOMESERVER: "https://call.ems.host"
jobs: jobs:
build: build:
name: Build name: Build

View file

@ -1,4 +1,10 @@
{ {
"default_server_config": {
"m.homeserver": {
"base_url": "https://call.ems.host",
"server_name": "call.ems.host"
}
},
"posthog": { "posthog": {
"api_key": "phc_MhClVy9DiV20vazSYIiedFkM5Xi3z1LPBwrdn9PYZQQ", "api_key": "phc_MhClVy9DiV20vazSYIiedFkM5Xi3z1LPBwrdn9PYZQQ",
"api_host": "https://app.posthog.com" "api_host": "https://app.posthog.com"

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

@ -114,8 +114,8 @@ export class PosthogAnalytics {
constructor(private readonly posthog: PostHog) { constructor(private readonly posthog: PostHog) {
const posthogConfig: PosthogSettings = { const posthogConfig: PosthogSettings = {
project_api_key: Config.instance.config.posthog?.api_key, project_api_key: Config.get().posthog?.api_key,
api_host: Config.instance.config.posthog?.api_host, api_host: Config.get().posthog?.api_host,
}; };
if (posthogConfig.project_api_key && posthogConfig.api_host) { if (posthogConfig.project_api_key && posthogConfig.api_host) {

View file

@ -14,14 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { import React, { FC, FormEvent, useCallback, useRef, useState } from "react";
FC,
FormEvent,
useCallback,
useRef,
useState,
useMemo,
} from "react";
import { useHistory, useLocation, Link } from "react-router-dom"; import { useHistory, useLocation, Link } from "react-router-dom";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
@ -29,11 +22,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 +34,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();
@ -75,14 +68,6 @@ export const LoginPage: FC = () => {
[login, location, history, homeserver, setClient] [login, location, history, homeserver, setClient]
); );
const homeserverHost = useMemo(() => {
try {
return new URL(homeserver).host;
} catch (error) {
return defaultHomeserverHost;
}
}, [homeserver]);
return ( return (
<> <>
<div className={styles.container}> <div className={styles.container}>
@ -102,7 +87,7 @@ export const LoginPage: FC = () => {
autoCorrect="off" autoCorrect="off"
autoCapitalize="none" autoCapitalize="none"
prefix="@" prefix="@"
suffix={`:${homeserverHost}`} suffix={`:${Config.defaultServerName()}`}
/> />
</FieldRow> </FieldRow>
<FieldRow> <FieldRow>

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

@ -22,13 +22,15 @@ import {
export class Config { export class Config {
private static internalInstance: Config; private static internalInstance: Config;
public static get instance(): Config {
if (!this.internalInstance) public static get(): ConfigOptions {
if (!this.internalInstance?.config)
throw new Error("Config instance read before config got initialized"); throw new Error("Config instance read before config got initialized");
return this.internalInstance; return this.internalInstance.config;
} }
public static init(): Promise<void> { public static init(): Promise<void> {
if (Config?.internalInstance?.initPromise) { if (Config.internalInstance?.initPromise) {
return Config.internalInstance.initPromise; return Config.internalInstance.initPromise;
} }
Config.internalInstance = new Config(); Config.internalInstance = new Config();
@ -41,8 +43,17 @@ export class Config {
return Config.internalInstance.initPromise; return Config.internalInstance.initPromise;
} }
public config: ResolvedConfigOptions; // Convenience accessors
private initPromise: Promise<void>; public static defaultHomeserverUrl(): string | undefined {
return Config.get().default_server_config["m.homeserver"].base_url;
}
public static defaultServerName(): string | undefined {
return Config.get().default_server_config["m.homeserver"].server_name;
}
public config?: ResolvedConfigOptions;
private initPromise?: Promise<void>;
} }
async function downloadConfig( async function downloadConfig(
@ -59,7 +70,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,33 @@ export interface ConfigOptions {
rageshake?: { rageshake?: {
submit_url: string; submit_url: string;
}; };
// Describes the default homeserver to use. The same format as Element Web
// (without identity servers as we don't use them).
default_server_config?: {
["m.homeserver"]: {
base_url: string;
server_name: string;
};
};
} }
// Overrides members from ConfigOptions that are always provided by the
// default config and are therefore non-optional.
export interface ResolvedConfigOptions extends ConfigOptions { export interface ResolvedConfigOptions extends ConfigOptions {
sentry: { default_server_config: {
DSN: string; ["m.homeserver"]: {
environment: string; base_url: string;
}; server_name: string;
rageshake: { };
submit_url: string;
}; };
} }
export const DEFAULT_CONFIG: ResolvedConfigOptions = { export const DEFAULT_CONFIG: ResolvedConfigOptions = {
sentry: { DSN: "", environment: "production" }, default_server_config: {
rageshake: { ["m.homeserver"]: {
submit_url: "https://element.io/bugreports/submit", base_url: "http://localhost:8008",
server_name: "localhost",
},
}, },
}; };

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,
@ -192,19 +191,21 @@ export class Initializer {
this.loadStates.sentry === LoadState.None && this.loadStates.sentry === LoadState.None &&
this.loadStates.config === LoadState.Loaded this.loadStates.config === LoadState.Loaded
) { ) {
Sentry.init({ if (Config.get().sentry?.DSN && Config.get().sentry?.environment) {
dsn: Config.instance.config.sentry?.DSN ?? DEFAULT_CONFIG.sentry.DSN, Sentry.init({
environment: dsn: Config.get().sentry?.DSN,
Config.instance.config.sentry.environment ?? environment: Config.get().sentry?.environment,
DEFAULT_CONFIG.sentry.environment, integrations: [
integrations: [ new Integrations.BrowserTracing({
new Integrations.BrowserTracing({ routingInstrumentation:
routingInstrumentation: Sentry.reactRouterV5Instrumentation(history),
Sentry.reactRouterV5Instrumentation(history), }),
}), ],
], tracesSampleRate: 1.0,
tracesSampleRate: 1.0, });
}); }
// Sentry is now 'loadeed' (even if we actually skipped starting
// it due to to not being configured)
this.loadStates.sentry = LoadState.Loaded; this.loadStates.sentry = LoadState.Loaded;
} }

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

@ -25,7 +25,6 @@ import { useClient } from "../ClientContext";
import { InspectorContext } from "../room/GroupCallInspector"; import { InspectorContext } from "../room/GroupCallInspector";
import { useModalTriggerState } from "../Modal"; import { useModalTriggerState } from "../Modal";
import { Config } from "../config/Config"; import { Config } from "../config/Config";
import { DEFAULT_CONFIG } from "../config/ConfigOptions";
interface RageShakeSubmitOptions { interface RageShakeSubmitOptions {
sendLogs: boolean; sendLogs: boolean;
@ -54,6 +53,10 @@ export function useSubmitRageshake(): {
const submitRageshake = useCallback( const submitRageshake = useCallback(
async (opts) => { async (opts) => {
if (!Config.get().rageshake?.submit_url) {
throw new Error("No rageshake URL is configured");
}
if (sending) { if (sending) {
return; return;
} }
@ -258,14 +261,10 @@ export function useSubmitRageshake(): {
); );
} }
await fetch( await fetch(Config.get().rageshake?.submit_url, {
Config.instance.config.rageshake?.submit_url ?? method: "POST",
DEFAULT_CONFIG.rageshake.submit_url, body,
{ });
method: "POST",
body,
}
);
setState({ sending: false, sent: true, error: null }); setState({ sending: false, sent: true, error: null });
} catch (error) { } catch (error) {