Conform more of the codebase with `noImplicitAny` and `strictNullChecks` (#25174

* Conform more of the codebase with `noImplicitAny` and `strictNullChecks`

* Fix tests

* Update src/vector/app.tsx
pull/25210/head
Michael Telatynski 2023-04-25 09:36:17 +01:00 committed by GitHub
parent a2da1eb79d
commit f5b8bccb65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 265 additions and 211 deletions

View File

@ -87,6 +87,7 @@ module.exports = {
// We don't need super strict typing in test utilities // We don't need super strict typing in test utilities
"@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-member-accessibility": "off", "@typescript-eslint/explicit-member-accessibility": "off",
"@typescript-eslint/ban-ts-comment": "off",
}, },
}, },
], ],

View File

@ -68,7 +68,7 @@
"@types/react": "17.0.58" "@types/react": "17.0.58"
}, },
"dependencies": { "dependencies": {
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.12.tgz", "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz",
"@matrix-org/react-sdk-module-api": "^0.0.4", "@matrix-org/react-sdk-module-api": "^0.0.4",
"gfm.css": "^1.1.2", "gfm.css": "^1.1.2",
"jsrsasign": "^10.5.25", "jsrsasign": "^10.5.25",
@ -104,6 +104,7 @@
"@svgr/webpack": "^5.5.0", "@svgr/webpack": "^5.5.0",
"@testing-library/react": "^12.1.5", "@testing-library/react": "^12.1.5",
"@types/jest": "^29.0.0", "@types/jest": "^29.0.0",
"@types/jitsi-meet": "^2.0.2",
"@types/jsrsasign": "^10.5.4", "@types/jsrsasign": "^10.5.4",
"@types/modernizr": "^3.5.3", "@types/modernizr": "^3.5.3",
"@types/node": "^16", "@types/node": "^16",

29
src/@types/jitsi-meet.d.ts vendored Normal file
View File

@ -0,0 +1,29 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
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 "jitsi-meet";
declare module "jitsi-meet" {
interface ExternalAPIEventCallbacks {
errorOccurred: (e: { error: Error & { isFatal?: boolean } }) => void;
}
interface JitsiMeetExternalAPI {
executeCommand(command: "setTileView", value: boolean): void;
}
}
export as namespace Jitsi;

View File

@ -17,10 +17,10 @@ limitations under the License.
import * as React from "react"; import * as React from "react";
import { _t } from "matrix-react-sdk/src/languageHandler"; import { _t } from "matrix-react-sdk/src/languageHandler";
import SdkConfig from "matrix-react-sdk/src/SdkConfig"; import SdkConfig from "matrix-react-sdk/src/SdkConfig";
// directly import the style here as this layer does not support rethemedex at this time so no matrix-react-sdk // directly import the style here as this layer does not support rethemedex at this time so no matrix-react-sdk
// PostCSS variables will be accessible. // PostCSS variables will be accessible.
import "../../../res/css/structures/ErrorView.pcss"; import "../../../res/css/structures/ErrorView.pcss";
import { ReactNode } from "react";
interface IProps { interface IProps {
onAccept(): void; onAccept(): void;
@ -91,7 +91,7 @@ const CompatibilityView: React.FC<IProps> = ({ onAccept }) => {
android = []; android = [];
} }
let mobileHeader = <h2 id="step2_heading">{_t("Use %(brand)s on mobile", { brand })}</h2>; let mobileHeader: ReactNode = <h2 id="step2_heading">{_t("Use %(brand)s on mobile", { brand })}</h2>;
if (!android.length && !ios) { if (!android.length && !ios) {
mobileHeader = null; mobileHeader = null;
} }

View File

@ -20,7 +20,7 @@ import SdkConfig from "matrix-react-sdk/src/SdkConfig";
import VectorAuthFooter from "./VectorAuthFooter"; import VectorAuthFooter from "./VectorAuthFooter";
export default class VectorAuthPage extends React.PureComponent { export default class VectorAuthPage extends React.PureComponent {
private static welcomeBackgroundUrl; private static welcomeBackgroundUrl?: string;
// cache the url as a static to prevent it changing without refreshing // cache the url as a static to prevent it changing without refreshing
private static getWelcomeBackgroundUrl(): string { private static getWelcomeBackgroundUrl(): string {

View File

@ -72,14 +72,14 @@ export default class Favicon {
// get height and width of the favicon // get height and width of the favicon
this.canvas.height = this.baseImage.height > 0 ? this.baseImage.height : 32; this.canvas.height = this.baseImage.height > 0 ? this.baseImage.height : 32;
this.canvas.width = this.baseImage.width > 0 ? this.baseImage.width : 32; this.canvas.width = this.baseImage.width > 0 ? this.baseImage.width : 32;
this.context = this.canvas.getContext("2d"); this.context = this.canvas.getContext("2d")!;
this.ready(); this.ready();
}; };
this.baseImage.setAttribute("src", lastIcon.getAttribute("href")); this.baseImage.setAttribute("src", lastIcon.getAttribute("href")!);
} else { } else {
this.canvas.height = this.baseImage.height = 32; this.canvas.height = this.baseImage.height = 32;
this.canvas.width = this.baseImage.width = 32; this.canvas.width = this.baseImage.width = 32;
this.context = this.canvas.getContext("2d"); this.context = this.canvas.getContext("2d")!;
this.ready(); this.ready();
} }
} }
@ -239,7 +239,7 @@ export default class Favicon {
const icons: HTMLLinkElement[] = []; const icons: HTMLLinkElement[] = [];
const links = window.document.getElementsByTagName("head")[0].getElementsByTagName("link"); const links = window.document.getElementsByTagName("head")[0].getElementsByTagName("link");
for (const link of links) { for (const link of links) {
if (/(^|\s)icon(\s|$)/i.test(link.getAttribute("rel"))) { if (link.hasAttribute("rel") && /(^|\s)icon(\s|$)/i.test(link.getAttribute("rel")!)) {
icons.push(link); icons.push(link);
} }
} }

View File

@ -25,7 +25,7 @@ import React, { ReactElement } from "react";
import PlatformPeg from "matrix-react-sdk/src/PlatformPeg"; import PlatformPeg from "matrix-react-sdk/src/PlatformPeg";
import { UserFriendlyError } from "matrix-react-sdk/src/languageHandler"; import { UserFriendlyError } from "matrix-react-sdk/src/languageHandler";
import AutoDiscoveryUtils from "matrix-react-sdk/src/utils/AutoDiscoveryUtils"; import AutoDiscoveryUtils from "matrix-react-sdk/src/utils/AutoDiscoveryUtils";
import { AutoDiscovery } from "matrix-js-sdk/src/autodiscovery"; import { AutoDiscovery, ClientConfig } from "matrix-js-sdk/src/autodiscovery";
import * as Lifecycle from "matrix-react-sdk/src/Lifecycle"; import * as Lifecycle from "matrix-react-sdk/src/Lifecycle";
import SdkConfig, { parseSsoRedirectOptions } from "matrix-react-sdk/src/SdkConfig"; import SdkConfig, { parseSsoRedirectOptions } from "matrix-react-sdk/src/SdkConfig";
import { IConfigOptions } from "matrix-react-sdk/src/IConfigOptions"; import { IConfigOptions } from "matrix-react-sdk/src/IConfigOptions";
@ -33,6 +33,8 @@ import { logger } from "matrix-js-sdk/src/logger";
import { createClient } from "matrix-js-sdk/src/matrix"; import { createClient } from "matrix-js-sdk/src/matrix";
import { SnakedObject } from "matrix-react-sdk/src/utils/SnakedObject"; import { SnakedObject } from "matrix-react-sdk/src/utils/SnakedObject";
import MatrixChat from "matrix-react-sdk/src/components/structures/MatrixChat"; import MatrixChat from "matrix-react-sdk/src/components/structures/MatrixChat";
import { ValidatedServerConfig } from "matrix-react-sdk/src/utils/ValidatedServerConfig";
import { QueryDict, encodeParams } from "matrix-js-sdk/src/utils";
import { parseQs } from "./url_utils"; import { parseQs } from "./url_utils";
import VectorBasePlatform from "./platform/VectorBasePlatform"; import VectorBasePlatform from "./platform/VectorBasePlatform";
@ -55,24 +57,19 @@ window.matrixLogger = logger;
// If we're in electron, we should never pass through a file:// URL otherwise // If we're in electron, we should never pass through a file:// URL otherwise
// the identity server will try to 302 the browser to it, which breaks horribly. // the identity server will try to 302 the browser to it, which breaks horribly.
// so in that instance, hardcode to use app.element.io for now instead. // so in that instance, hardcode to use app.element.io for now instead.
function makeRegistrationUrl(params: object): string { function makeRegistrationUrl(params: QueryDict): string {
let url; let url: string;
if (window.location.protocol === "vector:") { if (window.location.protocol === "vector:") {
url = "https://app.element.io/#/register"; url = "https://app.element.io/#/register";
} else { } else {
url = window.location.protocol + "//" + window.location.host + window.location.pathname + "#/register"; url = window.location.protocol + "//" + window.location.host + window.location.pathname + "#/register";
} }
const keys = Object.keys(params); const encodedParams = encodeParams(params);
for (let i = 0; i < keys.length; ++i) { if (encodedParams) {
if (i === 0) { url += "?" + encodedParams;
url += "?";
} else {
url += "&";
}
const k = keys[i];
url += k + "=" + encodeURIComponent(params[k]);
} }
return url; return url;
} }
@ -117,18 +114,19 @@ export async function loadApp(fragParams: {}): Promise<ReactElement> {
if (!hasPossibleToken && !isReturningFromSso && autoRedirect) { if (!hasPossibleToken && !isReturningFromSso && autoRedirect) {
logger.log("Bypassing app load to redirect to SSO"); logger.log("Bypassing app load to redirect to SSO");
const tempCli = createClient({ const tempCli = createClient({
baseUrl: config.validated_server_config.hsUrl, baseUrl: config.validated_server_config!.hsUrl,
idBaseUrl: config.validated_server_config.isUrl, idBaseUrl: config.validated_server_config!.isUrl,
}); });
PlatformPeg.get().startSingleSignOn(tempCli, "sso", `/${getScreenFromLocation(window.location).screen}`); PlatformPeg.get()!.startSingleSignOn(tempCli, "sso", `/${getScreenFromLocation(window.location).screen}`);
// We return here because startSingleSignOn() will asynchronously redirect us. We don't // We return here because startSingleSignOn() will asynchronously redirect us. We don't
// care to wait for it, and don't want to show any UI while we wait (not even half a welcome // care to wait for it, and don't want to show any UI while we wait (not even half a welcome
// page). As such, just don't even bother loading the MatrixChat component. // page). As such, just don't even bother loading the MatrixChat component.
return; return <React.Fragment />;
} }
const defaultDeviceName = snakedConfig.get("default_device_display_name") ?? platform.getDefaultDeviceDisplayName(); const defaultDeviceName =
snakedConfig.get("default_device_display_name") ?? platform?.getDefaultDeviceDisplayName();
return ( return (
<MatrixChat <MatrixChat
@ -146,7 +144,7 @@ export async function loadApp(fragParams: {}): Promise<ReactElement> {
} }
async function verifyServerConfig(): Promise<IConfigOptions> { async function verifyServerConfig(): Promise<IConfigOptions> {
let validatedConfig; let validatedConfig: ValidatedServerConfig;
try { try {
logger.log("Verifying homeserver configuration"); logger.log("Verifying homeserver configuration");
@ -197,7 +195,7 @@ async function verifyServerConfig(): Promise<IConfigOptions> {
} }
} }
let discoveryResult = null; let discoveryResult: ClientConfig | undefined;
if (wkConfig) { if (wkConfig) {
logger.log("Config uses a default_server_config - validating object"); logger.log("Config uses a default_server_config - validating object");
discoveryResult = await AutoDiscovery.fromDiscoveryConfig(wkConfig); discoveryResult = await AutoDiscovery.fromDiscoveryConfig(wkConfig);

View File

@ -18,16 +18,16 @@ import type { IConfigOptions } from "matrix-react-sdk/src/IConfigOptions";
// Load the config file. First try to load up a domain-specific config of the // Load the config file. First try to load up a domain-specific config of the
// form "config.$domain.json" and if that fails, fall back to config.json. // form "config.$domain.json" and if that fails, fall back to config.json.
export async function getVectorConfig(relativeLocation = ""): Promise<IConfigOptions> { export async function getVectorConfig(relativeLocation = ""): Promise<IConfigOptions | undefined> {
if (relativeLocation !== "" && !relativeLocation.endsWith("/")) relativeLocation += "/"; if (relativeLocation !== "" && !relativeLocation.endsWith("/")) relativeLocation += "/";
const specificConfigPromise = getConfig(`${relativeLocation}config.${document.domain}.json`); const specificConfigPromise = getConfig(`${relativeLocation}config.${window.location.hostname}.json`);
const generalConfigPromise = getConfig(relativeLocation + "config.json"); const generalConfigPromise = getConfig(relativeLocation + "config.json");
try { try {
const configJson = await specificConfigPromise; const configJson = await specificConfigPromise;
// 404s succeed with an empty json config, so check that there are keys // 404s succeed with an empty json config, so check that there are keys
if (Object.keys(configJson).length === 0) { if (!configJson || Object.keys(configJson).length === 0) {
throw new Error(); // throw to enter the catch throw new Error(); // throw to enter the catch
} }
return configJson; return configJson;
@ -36,7 +36,7 @@ export async function getVectorConfig(relativeLocation = ""): Promise<IConfigOpt
} }
} }
async function getConfig(configJsonFilename: string): Promise<IConfigOptions> { async function getConfig(configJsonFilename: string): Promise<IConfigOptions | undefined> {
const url = new URL(configJsonFilename, window.location.href); const url = new URL(configJsonFilename, window.location.href);
url.searchParams.set("cachebuster", Date.now().toString()); url.searchParams.set("cachebuster", Date.now().toString());
const res = await fetch(url, { const res = await fetch(url, {

View File

@ -71,7 +71,7 @@ function checkBrowserFeatures(): boolean {
// ES2019: http://262.ecma-international.org/10.0/#sec-object.fromentries // ES2019: http://262.ecma-international.org/10.0/#sec-object.fromentries
window.Modernizr.addTest("objectfromentries", () => typeof window.Object?.fromEntries === "function"); window.Modernizr.addTest("objectfromentries", () => typeof window.Object?.fromEntries === "function");
const featureList = Object.keys(window.Modernizr); const featureList = Object.keys(window.Modernizr) as Array<keyof ModernizrStatic>;
let featureComplete = true; let featureComplete = true;
for (const feature of featureList) { for (const feature of featureList) {
@ -240,7 +240,7 @@ start().catch((err) => {
logger.error(err); logger.error(err);
// show the static error in an iframe to not lose any context / console data // show the static error in an iframe to not lose any context / console data
// with some basic styling to make the iframe full page // with some basic styling to make the iframe full page
delete document.body.style.height; document.body.style.removeProperty("height");
const iframe = document.createElement("iframe"); const iframe = document.createElement("iframe");
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - typescript seems to only like the IE syntax for iframe sandboxing // @ts-ignore - typescript seems to only like the IE syntax for iframe sandboxing
@ -254,5 +254,5 @@ start().catch((err) => {
iframe.style.right = "0"; iframe.style.right = "0";
iframe.style.bottom = "0"; iframe.style.bottom = "0";
iframe.style.border = "0"; iframe.style.border = "0";
document.getElementById("matrixchat").appendChild(iframe); document.getElementById("matrixchat")?.appendChild(iframe);
}); });

View File

@ -67,7 +67,7 @@ export async function loadConfig(): Promise<void> {
// granular settings are loaded correctly and to avoid duplicating the override logic for the theme. // granular settings are loaded correctly and to avoid duplicating the override logic for the theme.
// //
// Note: this isn't called twice for some wrappers, like the Jitsi wrapper. // Note: this isn't called twice for some wrappers, like the Jitsi wrapper.
const platformConfig = await PlatformPeg.get().getConfig(); const platformConfig = await PlatformPeg.get()?.getConfig();
if (platformConfig) { if (platformConfig) {
SdkConfig.put(platformConfig); SdkConfig.put(platformConfig);
} else { } else {
@ -119,7 +119,7 @@ export function loadOlm(): Promise<void> {
export async function loadLanguage(): Promise<void> { export async function loadLanguage(): Promise<void> {
const prefLang = SettingsStore.getValue("language", null, /*excludeDefault=*/ true); const prefLang = SettingsStore.getValue("language", null, /*excludeDefault=*/ true);
let langs = []; let langs: string[] = [];
if (!prefLang) { if (!prefLang) {
languageHandler.getLanguagesFromBrowser().forEach((l) => { languageHandler.getLanguagesFromBrowser().forEach((l) => {
@ -163,7 +163,7 @@ export async function showError(title: string, messages?: string[]): Promise<voi
); );
} }
export async function showIncompatibleBrowser(onAccept): Promise<void> { export async function showIncompatibleBrowser(onAccept: () => void): Promise<void> {
const CompatibilityView = ( const CompatibilityView = (
await import( await import(
/* webpackChunkName: "compatibility-view" */ /* webpackChunkName: "compatibility-view" */

View File

@ -30,6 +30,14 @@ import { IConfigOptions } from "matrix-react-sdk/src/IConfigOptions";
import { SnakedObject } from "matrix-react-sdk/src/utils/SnakedObject"; import { SnakedObject } from "matrix-react-sdk/src/utils/SnakedObject";
import { ElementWidgetCapabilities } from "matrix-react-sdk/src/stores/widgets/ElementWidgetCapabilities"; import { ElementWidgetCapabilities } from "matrix-react-sdk/src/stores/widgets/ElementWidgetCapabilities";
import type {
JitsiMeetExternalAPIConstructor,
ExternalAPIEventCallbacks,
JitsiMeetExternalAPI as _JitsiMeetExternalAPI,
AudioMuteStatusChangedEvent,
LogEvent,
VideoMuteStatusChangedEvent,
} from "jitsi-meet";
import { getVectorConfig } from "../getconfig"; import { getVectorConfig } from "../getconfig";
// We have to trick webpack into loading our CSS for us. // We have to trick webpack into loading our CSS for us.
@ -40,7 +48,7 @@ const JITSI_OPENIDTOKEN_JWT_AUTH = "openidtoken-jwt";
// Dev note: we use raw JS without many dependencies to reduce bundle size. // Dev note: we use raw JS without many dependencies to reduce bundle size.
// We do not need all of React to render a Jitsi conference. // We do not need all of React to render a Jitsi conference.
declare let JitsiMeetExternalAPI: any; declare let JitsiMeetExternalAPI: JitsiMeetExternalAPIConstructor;
let inConference = false; let inConference = false;
@ -60,8 +68,8 @@ let isVideoChannel: boolean;
let supportsScreensharing: boolean; let supportsScreensharing: boolean;
let language: string; let language: string;
let widgetApi: WidgetApi; let widgetApi: WidgetApi | undefined;
let meetApi: any; // JitsiMeetExternalAPI let meetApi: _JitsiMeetExternalAPI | undefined;
let skipOurWelcomeScreen = false; let skipOurWelcomeScreen = false;
const setupCompleted = (async (): Promise<string | void> => { const setupCompleted = (async (): Promise<string | void> => {
@ -102,12 +110,12 @@ const setupCompleted = (async (): Promise<string | void> => {
} }
// Set this up as early as possible because Element will be hitting it almost immediately. // Set this up as early as possible because Element will be hitting it almost immediately.
let widgetApiReady: Promise<void>; let widgetApiReady: Promise<void> | undefined;
if (parentUrl && widgetId) { if (parentUrl && widgetId) {
const parentOrigin = new URL(qsParam("parentUrl")).origin; const parentOrigin = new URL(qsParam("parentUrl")).origin;
widgetApi = new WidgetApi(qsParam("widgetId"), parentOrigin); widgetApi = new WidgetApi(qsParam("widgetId"), parentOrigin);
widgetApiReady = new Promise<void>((resolve) => widgetApi.once("ready", resolve)); widgetApiReady = new Promise<void>((resolve) => widgetApi!.once("ready", resolve));
widgetApi.requestCapabilities(VideoConferenceCapabilities); widgetApi.requestCapabilities(VideoConferenceCapabilities);
// jitsi cannot work in a popup if auth token is provided because widgetApi is not available there // jitsi cannot work in a popup if auth token is provided because widgetApi is not available there
@ -122,7 +130,7 @@ const setupCompleted = (async (): Promise<string | void> => {
action: WidgetApiAction, action: WidgetApiAction,
handler: (request: IWidgetApiRequestData) => Promise<void>, handler: (request: IWidgetApiRequestData) => Promise<void>,
): void => { ): void => {
widgetApi.on(`action:${action}`, async (ev: CustomEvent<IWidgetApiRequest>) => { widgetApi!.on(`action:${action}`, async (ev: CustomEvent<IWidgetApiRequest>) => {
ev.preventDefault(); ev.preventDefault();
await setupCompleted; await setupCompleted;
@ -138,7 +146,7 @@ const setupCompleted = (async (): Promise<string | void> => {
} }
} }
await widgetApi.transport.reply(ev.detail, response); await widgetApi!.transport.reply(ev.detail, response);
}); });
}; };
@ -149,7 +157,7 @@ const setupCompleted = (async (): Promise<string | void> => {
if (force === true) { if (force === true) {
meetApi?.dispose(); meetApi?.dispose();
notifyHangup(); notifyHangup();
meetApi = null; meetApi = undefined;
closeConference(); closeConference();
} else { } else {
meetApi?.executeCommand("hangup"); meetApi?.executeCommand("hangup");
@ -212,9 +220,10 @@ const setupCompleted = (async (): Promise<string | void> => {
// We've reached the point where we have to wait for the config, so do that then parse it. // We've reached the point where we have to wait for the config, so do that then parse it.
const instanceConfig = new SnakedObject<IConfigOptions>((await configPromise) ?? <IConfigOptions>{}); const instanceConfig = new SnakedObject<IConfigOptions>((await configPromise) ?? <IConfigOptions>{});
const jitsiConfig = instanceConfig.get("jitsi_widget") ?? {}; const jitsiConfig = instanceConfig.get("jitsi_widget");
skipOurWelcomeScreen = if (jitsiConfig) {
new SnakedObject<IConfigOptions["jitsi_widget"]>(jitsiConfig).get("skip_built_in_welcome_screen") ?? false; skipOurWelcomeScreen = new SnakedObject(jitsiConfig).get("skip_built_in_welcome_screen") ?? false;
}
// Either reveal the prejoin screen, or skip straight to Jitsi depending on the config. // Either reveal the prejoin screen, or skip straight to Jitsi depending on the config.
// We don't set up the call yet though as this might lead to failure without the widget API. // We don't set up the call yet though as this might lead to failure without the widget API.
@ -232,12 +241,12 @@ const setupCompleted = (async (): Promise<string | void> => {
enableJoinButton(); // always enable the button enableJoinButton(); // always enable the button
} catch (e) { } catch (e) {
logger.error("Error setting up Jitsi widget", e); logger.error("Error setting up Jitsi widget", e);
document.getElementById("widgetActionContainer").innerText = "Failed to load Jitsi widget"; document.getElementById("widgetActionContainer")!.innerText = "Failed to load Jitsi widget";
} }
})(); })();
function enableJoinButton(): void { function enableJoinButton(): void {
document.getElementById("joinButton").onclick = (): Promise<void> => joinConference(); document.getElementById("joinButton")!.onclick = (): Promise<void> => joinConference();
} }
function switchVisibleContainers(): void { function switchVisibleContainers(): void {
@ -251,9 +260,9 @@ function switchVisibleContainers(): void {
} }
function toggleConferenceVisibility(inConference: boolean): void { function toggleConferenceVisibility(inConference: boolean): void {
document.getElementById("jitsiContainer").style.visibility = inConference ? "unset" : "hidden"; document.getElementById("jitsiContainer")!.style.visibility = inConference ? "unset" : "hidden";
// Video rooms have a separate UI for joining, so they should never show our join button // Video rooms have a separate UI for joining, so they should never show our join button
document.getElementById("joinButtonContainer").style.visibility = document.getElementById("joinButtonContainer")!.style.visibility =
inConference || isVideoChannel ? "hidden" : "unset"; inConference || isVideoChannel ? "hidden" : "unset";
} }
@ -311,7 +320,7 @@ async function notifyHangup(errorMessage?: string): Promise<void> {
function closeConference(): void { function closeConference(): void {
switchVisibleContainers(); switchVisibleContainers();
document.getElementById("jitsiContainer").innerHTML = ""; document.getElementById("jitsiContainer")!.innerHTML = "";
if (skipOurWelcomeScreen) { if (skipOurWelcomeScreen) {
skipToJitsiSplashScreen(); skipToJitsiSplashScreen();
@ -349,17 +358,17 @@ function mapLanguage(language: string): string {
// and a non-nullish input specifies the label of a specific device to use. // and a non-nullish input specifies the label of a specific device to use.
// Same for video inputs. // Same for video inputs.
async function joinConference(audioInput?: string | null, videoInput?: string | null): Promise<void> { async function joinConference(audioInput?: string | null, videoInput?: string | null): Promise<void> {
let jwt; let jwt: string | undefined;
if (jitsiAuth === JITSI_OPENIDTOKEN_JWT_AUTH) { if (jitsiAuth === JITSI_OPENIDTOKEN_JWT_AUTH) {
// See https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification // See https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification
const openIdToken: IOpenIDCredentials = await widgetApi.requestOpenIDConnectToken(); const openIdToken = await widgetApi?.requestOpenIDConnectToken();
logger.log("Got OpenID Connect token"); logger.log("Got OpenID Connect token");
if (!openIdToken.access_token) { if (!openIdToken?.access_token) {
// eslint-disable-line camelcase // eslint-disable-line camelcase
// We've failing to get a token, don't try to init conference // We've failing to get a token, don't try to init conference
logger.warn("Expected to have an OpenID credential, cannot initialize widget."); logger.warn("Expected to have an OpenID credential, cannot initialize widget.");
document.getElementById("widgetActionContainer").innerText = "Failed to load Jitsi widget"; document.getElementById("widgetActionContainer")!.innerText = "Failed to load Jitsi widget";
return; return;
} }
jwt = createJWTToken(openIdToken); jwt = createJWTToken(openIdToken);
@ -376,7 +385,7 @@ async function joinConference(audioInput?: string | null, videoInput?: string |
const options = { const options = {
width: "100%", width: "100%",
height: "100%", height: "100%",
parentNode: document.querySelector("#jitsiContainer"), parentNode: document.querySelector("#jitsiContainer") ?? undefined,
roomName: conferenceId, roomName: conferenceId,
devices: { devices: {
audioInput, audioInput,
@ -434,13 +443,13 @@ async function joinConference(audioInput?: string | null, videoInput?: string |
// (regardless of video on or off) // (regardless of video on or off)
meetApi.on("videoConferenceJoined", onVideoConferenceJoined); meetApi.on("videoConferenceJoined", onVideoConferenceJoined);
meetApi.on("videoConferenceLeft", onVideoConferenceLeft); meetApi.on("videoConferenceLeft", onVideoConferenceLeft);
meetApi.on("readyToClose", closeConference); meetApi.on("readyToClose", closeConference as ExternalAPIEventCallbacks["readyToClose"]);
meetApi.on("errorOccurred", onErrorOccurred); meetApi.on("errorOccurred", onErrorOccurred);
meetApi.on("audioMuteStatusChanged", onAudioMuteStatusChanged); meetApi.on("audioMuteStatusChanged", onAudioMuteStatusChanged);
meetApi.on("videoMuteStatusChanged", onVideoMuteStatusChanged); meetApi.on("videoMuteStatusChanged", onVideoMuteStatusChanged);
["videoConferenceJoined", "participantJoined", "participantLeft"].forEach((event) => { (["videoConferenceJoined", "participantJoined", "participantLeft"] as const).forEach((event) => {
meetApi.on(event, updateParticipants); meetApi!.on(event, updateParticipants);
}); });
// Patch logs into rageshakes // Patch logs into rageshakes
@ -456,9 +465,9 @@ const onVideoConferenceJoined = (): void => {
// We can't just use these commands immediately after creating the // We can't just use these commands immediately after creating the
// iframe, because there's *another* bug where they can crash Jitsi by // iframe, because there's *another* bug where they can crash Jitsi by
// racing with its startup process. // racing with its startup process.
if (displayName) meetApi.executeCommand("displayName", displayName); if (displayName) meetApi?.executeCommand("displayName", displayName);
// This doesn't have a userInfo equivalent, so has to be set via commands // This doesn't have a userInfo equivalent, so has to be set via commands
if (avatarUrl) meetApi.executeCommand("avatarUrl", avatarUrl); if (avatarUrl) meetApi?.executeCommand("avatarUrl", avatarUrl);
if (widgetApi) { if (widgetApi) {
// ignored promise because we don't care if it works // ignored promise because we don't care if it works
@ -468,30 +477,30 @@ const onVideoConferenceJoined = (): void => {
} }
// Video rooms should start in tile mode // Video rooms should start in tile mode
if (isVideoChannel) meetApi.executeCommand("setTileView", true); if (isVideoChannel) meetApi?.executeCommand("setTileView", true);
}; };
const onVideoConferenceLeft = (): void => { const onVideoConferenceLeft = (): void => {
notifyHangup(); notifyHangup();
meetApi = null; meetApi = undefined;
}; };
const onErrorOccurred = ({ error }): void => { const onErrorOccurred = ({ error }: Parameters<ExternalAPIEventCallbacks["errorOccurred"]>[0]): void => {
if (error.isFatal) { if (error.isFatal) {
// We got disconnected. Since Jitsi Meet might send us back to the // We got disconnected. Since Jitsi Meet might send us back to the
// prejoin screen, we're forced to act as if we hung up entirely. // prejoin screen, we're forced to act as if we hung up entirely.
notifyHangup(error.message); notifyHangup(error.message);
meetApi = null; meetApi = undefined;
closeConference(); closeConference();
} }
}; };
const onAudioMuteStatusChanged = ({ muted }): void => { const onAudioMuteStatusChanged = ({ muted }: AudioMuteStatusChangedEvent): void => {
const action = muted ? ElementWidgetActions.MuteAudio : ElementWidgetActions.UnmuteAudio; const action = muted ? ElementWidgetActions.MuteAudio : ElementWidgetActions.UnmuteAudio;
widgetApi?.transport.send(action, {}); widgetApi?.transport.send(action, {});
}; };
const onVideoMuteStatusChanged = ({ muted }): void => { const onVideoMuteStatusChanged = ({ muted }: VideoMuteStatusChangedEvent): void => {
if (muted) { if (muted) {
// Jitsi Meet always sends a "video muted" event directly before // Jitsi Meet always sends a "video muted" event directly before
// hanging up, which we need to ignore by padding the timeout here, // hanging up, which we need to ignore by padding the timeout here,
@ -507,8 +516,9 @@ const onVideoMuteStatusChanged = ({ muted }): void => {
const updateParticipants = (): void => { const updateParticipants = (): void => {
widgetApi?.transport.send(ElementWidgetActions.CallParticipants, { widgetApi?.transport.send(ElementWidgetActions.CallParticipants, {
participants: meetApi.getParticipantsInfo(), participants: meetApi?.getParticipantsInfo(),
}); });
}; };
const onLog = ({ logLevel, args }): void => (parent as unknown as typeof global).mx_rage_logger?.log(logLevel, ...args); const onLog = ({ logLevel, args }: LogEvent): void =>
(parent as unknown as typeof global).mx_rage_logger?.log(logLevel, ...args);

View File

@ -31,17 +31,17 @@ function renderConfigError(message: string): void {
} }
async function initPage(): Promise<void> { async function initPage(): Promise<void> {
document.getElementById("back_to_element_button").onclick = onBackToElementClick; document.getElementById("back_to_element_button")!.onclick = onBackToElementClick;
const config = await getVectorConfig(".."); const config = await getVectorConfig("..");
// We manually parse the config similar to how validateServerConfig works because // We manually parse the config similar to how validateServerConfig works because
// calling that function pulls in roughly 4mb of JS we don't use. // calling that function pulls in roughly 4mb of JS we don't use.
const wkConfig = config["default_server_config"]; // overwritten later under some conditions const wkConfig = config?.["default_server_config"]; // overwritten later under some conditions
const serverName = config["default_server_name"]; const serverName = config?.["default_server_name"];
const defaultHsUrl = config["default_hs_url"]; const defaultHsUrl = config?.["default_hs_url"];
const defaultIsUrl = config["default_is_url"]; const defaultIsUrl = config?.["default_is_url"];
const incompatibleOptions = [wkConfig, serverName, defaultHsUrl].filter((i) => !!i); const incompatibleOptions = [wkConfig, serverName, defaultHsUrl].filter((i) => !!i);
if (incompatibleOptions.length > 1) { if (incompatibleOptions.length > 1) {
@ -54,13 +54,13 @@ async function initPage(): Promise<void> {
return renderConfigError("Invalid configuration: no default server specified."); return renderConfigError("Invalid configuration: no default server specified.");
} }
let hsUrl = ""; let hsUrl: string | undefined;
let isUrl = ""; let isUrl: string | undefined;
if (wkConfig && wkConfig["m.homeserver"]) { if (typeof wkConfig?.["m.homeserver"]?.["base_url"] === "string") {
hsUrl = wkConfig["m.homeserver"]["base_url"]; hsUrl = wkConfig["m.homeserver"]["base_url"];
if (wkConfig["m.identity_server"]) { if (typeof wkConfig["m.identity_server"]?.["base_url"] === "string") {
isUrl = wkConfig["m.identity_server"]["base_url"]; isUrl = wkConfig["m.identity_server"]["base_url"];
} }
} }
@ -96,17 +96,19 @@ async function initPage(): Promise<void> {
if (isUrl && !isUrl.endsWith("/")) isUrl += "/"; if (isUrl && !isUrl.endsWith("/")) isUrl += "/";
if (hsUrl !== "https://matrix.org/") { if (hsUrl !== "https://matrix.org/") {
(document.getElementById("configure_element_button") as HTMLAnchorElement).href = let url = "https://mobile.element.io?hs_url=" + encodeURIComponent(hsUrl);
"https://mobile.element.io?hs_url=" + encodeURIComponent(hsUrl) + "&is_url=" + encodeURIComponent(isUrl);
document.getElementById("step1_heading").innerHTML = "1: Install the app";
document.getElementById("step2_container").style.display = "block";
document.getElementById("hs_url").innerText = hsUrl;
if (isUrl) { if (isUrl) {
document.getElementById("custom_is").style.display = "block"; document.getElementById("custom_is")!.style.display = "block";
document.getElementById("is_url").style.display = "block"; document.getElementById("is_url")!.style.display = "block";
document.getElementById("is_url").innerText = isUrl; document.getElementById("is_url")!.innerText = isUrl;
url += "&is_url=" + encodeURIComponent(isUrl ?? "");
} }
(document.getElementById("configure_element_button") as HTMLAnchorElement).href = url;
document.getElementById("step1_heading")!.innerHTML = "1: Install the app";
document.getElementById("step2_container")!.style.display = "block";
document.getElementById("hs_url")!.innerText = hsUrl;
} }
} }

View File

@ -79,7 +79,7 @@ function platformFriendlyName(): string {
function onAction(payload: ActionPayload): void { function onAction(payload: ActionPayload): void {
// Whitelist payload actions, no point sending most across // Whitelist payload actions, no point sending most across
if (["call_state"].includes(payload.action)) { if (["call_state"].includes(payload.action)) {
window.electron.send("app_onAction", payload); window.electron!.send("app_onAction", payload);
} }
} }
@ -105,6 +105,10 @@ export default class ElectronPlatform extends VectorBasePlatform {
public constructor() { public constructor() {
super(); super();
if (!window.electron) {
throw new Error("Cannot instantiate ElectronPlatform, window.electron is not set");
}
dis.register(onAction); dis.register(onAction);
/* /*
IPC Call `check_updates` returns: IPC Call `check_updates` returns:
@ -135,12 +139,12 @@ export default class ElectronPlatform extends VectorBasePlatform {
const key = `DOWNLOAD_TOAST_${id}`; const key = `DOWNLOAD_TOAST_${id}`;
const onAccept = (): void => { const onAccept = (): void => {
window.electron.send("userDownloadAction", { id, open: true }); window.electron!.send("userDownloadAction", { id, open: true });
ToastStore.sharedInstance().dismissToast(key); ToastStore.sharedInstance().dismissToast(key);
}; };
const onDismiss = (): void => { const onDismiss = (): void => {
window.electron.send("userDownloadAction", { id }); window.electron!.send("userDownloadAction", { id });
}; };
ToastStore.sharedInstance().addOrReplaceToast({ ToastStore.sharedInstance().addOrReplaceToast({
@ -164,7 +168,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate); BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate);
} }
public async getConfig(): Promise<IConfigOptions> { public async getConfig(): Promise<IConfigOptions | undefined> {
return this.ipc.call("getConfig"); return this.ipc.call("getConfig");
} }
@ -212,7 +216,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
if (this.notificationCount === count) return; if (this.notificationCount === count) return;
super.setNotificationCount(count); super.setNotificationCount(count);
window.electron.send("setBadgeCount", count); window.electron!.send("setBadgeCount", count);
} }
public supportsNotifications(): boolean { public supportsNotifications(): boolean {
@ -252,7 +256,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
} }
public loudNotification(ev: MatrixEvent, room: Room): void { public loudNotification(ev: MatrixEvent, room: Room): void {
window.electron.send("loudNotification"); window.electron!.send("loudNotification");
} }
public needsUrlTooltips(): boolean { public needsUrlTooltips(): boolean {
@ -288,14 +292,14 @@ export default class ElectronPlatform extends VectorBasePlatform {
public startUpdateCheck(): void { public startUpdateCheck(): void {
super.startUpdateCheck(); super.startUpdateCheck();
window.electron.send("check_updates"); window.electron!.send("check_updates");
} }
public installUpdate(): void { public installUpdate(): void {
// IPC to the main process to install the update, since quitAndInstall // IPC to the main process to install the update, since quitAndInstall
// doesn't fire the before-quit event so the main process needs to know // doesn't fire the before-quit event so the main process needs to know
// it should exit. // it should exit.
window.electron.send("install_update"); window.electron!.send("install_update");
} }
public getDefaultDeviceDisplayName(): string { public getDefaultDeviceDisplayName(): string {

View File

@ -33,6 +33,9 @@ export class IPCManager {
private readonly sendChannel: ElectronChannel = "ipcCall", private readonly sendChannel: ElectronChannel = "ipcCall",
private readonly recvChannel: ElectronChannel = "ipcReply", private readonly recvChannel: ElectronChannel = "ipcReply",
) { ) {
if (!window.electron) {
throw new Error("Cannot instantiate ElectronPlatform, window.electron is not set");
}
window.electron.on(this.recvChannel, this.onIpcReply); window.electron.on(this.recvChannel, this.onIpcReply);
} }
@ -42,7 +45,7 @@ export class IPCManager {
const deferred = defer<any>(); const deferred = defer<any>();
this.pendingIpcCalls[ipcCallId] = deferred; this.pendingIpcCalls[ipcCallId] = deferred;
// Maybe add a timeout to these? Probably not necessary. // Maybe add a timeout to these? Probably not necessary.
window.electron.send(this.sendChannel, { id: ipcCallId, name, args }); window.electron!.send(this.sendChannel, { id: ipcCallId, name, args });
return deferred.promise; return deferred.promise;
} }

View File

@ -19,6 +19,7 @@ import BaseEventIndexManager, {
IEventAndProfile, IEventAndProfile,
IIndexStats, IIndexStats,
ISearchArgs, ISearchArgs,
ILoadArgs,
} from "matrix-react-sdk/src/indexing/BaseEventIndexManager"; } from "matrix-react-sdk/src/indexing/BaseEventIndexManager";
import { IMatrixProfile, IEventWithRoomId as IMatrixEvent, IResultRoomEvents } from "matrix-js-sdk/src/@types/search"; import { IMatrixProfile, IEventWithRoomId as IMatrixEvent, IResultRoomEvents } from "matrix-js-sdk/src/@types/search";
@ -75,7 +76,7 @@ export class SeshatIndexManager extends BaseEventIndexManager {
return this.ipc.call("removeCrawlerCheckpoint", checkpoint); return this.ipc.call("removeCrawlerCheckpoint", checkpoint);
} }
public async loadFileEvents(args): Promise<IEventAndProfile[]> { public async loadFileEvents(args: ILoadArgs): Promise<IEventAndProfile[]> {
return this.ipc.call("loadFileEvents", args); return this.ipc.call("loadFileEvents", args);
} }

View File

@ -30,7 +30,7 @@ import Favicon from "../../favicon";
export default abstract class VectorBasePlatform extends BasePlatform { export default abstract class VectorBasePlatform extends BasePlatform {
protected _favicon: Favicon; protected _favicon: Favicon;
public async getConfig(): Promise<IConfigOptions> { public async getConfig(): Promise<IConfigOptions | undefined> {
return getVectorConfig(); return getVectorConfig();
} }

View File

@ -40,6 +40,8 @@ function getNormalizedAppVersion(version: string): string {
} }
export default class WebPlatform extends VectorBasePlatform { export default class WebPlatform extends VectorBasePlatform {
private static readonly VERSION = process.env.VERSION!; // baked in by Webpack
public constructor() { public constructor() {
super(); super();
// Register service worker if available on this platform // Register service worker if available on this platform
@ -101,7 +103,7 @@ export default class WebPlatform extends VectorBasePlatform {
} }
public getAppVersion(): Promise<string> { public getAppVersion(): Promise<string> {
return Promise.resolve(getNormalizedAppVersion(process.env.VERSION)); return Promise.resolve(getNormalizedAppVersion(WebPlatform.VERSION));
} }
public startUpdater(): void { public startUpdater(): void {
@ -113,7 +115,7 @@ export default class WebPlatform extends VectorBasePlatform {
// //
// Ideally, loading an old copy would be impossible with the // Ideally, loading an old copy would be impossible with the
// cache-control: nocache HTTP header set, but Firefox doesn't always obey it :/ // cache-control: nocache HTTP header set, but Firefox doesn't always obey it :/
console.log("startUpdater, current version is " + getNormalizedAppVersion(process.env.VERSION)); console.log("startUpdater, current version is " + getNormalizedAppVersion(WebPlatform.VERSION));
this.pollForUpdate((version: string, newVersion: string) => { this.pollForUpdate((version: string, newVersion: string) => {
const query = parseQs(location); const query = parseQs(location);
if (query.updated) { if (query.updated) {
@ -144,7 +146,7 @@ export default class WebPlatform extends VectorBasePlatform {
): Promise<UpdateStatus> => { ): Promise<UpdateStatus> => {
return this.getMostRecentVersion().then( return this.getMostRecentVersion().then(
(mostRecentVersion) => { (mostRecentVersion) => {
const currentVersion = getNormalizedAppVersion(process.env.VERSION); const currentVersion = getNormalizedAppVersion(WebPlatform.VERSION);
if (currentVersion !== mostRecentVersion) { if (currentVersion !== mostRecentVersion) {
if (this.shouldShowUpdate(mostRecentVersion)) { if (this.shouldShowUpdate(mostRecentVersion)) {

View File

@ -22,7 +22,7 @@ import MatrixChatType from "matrix-react-sdk/src/components/structures/MatrixCha
import { parseQsFromFragment } from "./url_utils"; import { parseQsFromFragment } from "./url_utils";
let lastLocationHashSet: string = null; let lastLocationHashSet: string | null = null;
export function getScreenFromLocation(location: Location): { screen: string; params: QueryDict } { export function getScreenFromLocation(location: Location): { screen: string; params: QueryDict } {
const fragparts = parseQsFromFragment(location); const fragparts = parseQsFromFragment(location);

View File

@ -28,6 +28,8 @@ import MockHttpBackend from "matrix-mock-request";
import { ValidatedServerConfig } from "matrix-react-sdk/src/utils/ValidatedServerConfig"; import { ValidatedServerConfig } from "matrix-react-sdk/src/utils/ValidatedServerConfig";
import { IndexedDBCryptoStore } from "matrix-js-sdk/src/crypto/store/indexeddb-crypto-store"; import { IndexedDBCryptoStore } from "matrix-js-sdk/src/crypto/store/indexeddb-crypto-store";
import { QueryDict, sleep } from "matrix-js-sdk/src/utils"; import { QueryDict, sleep } from "matrix-js-sdk/src/utils";
import { IConfigOptions } from "matrix-react-sdk/src/IConfigOptions";
import { ActionPayload } from "matrix-react-sdk/src/dispatcher/payloads";
import "../jest-mocks"; import "../jest-mocks";
import WebPlatform from "../../src/vector/platform/WebPlatform"; import WebPlatform from "../../src/vector/platform/WebPlatform";
@ -38,26 +40,21 @@ const DEFAULT_HS_URL = "http://my_server";
const DEFAULT_IS_URL = "http://my_is"; const DEFAULT_IS_URL = "http://my_is";
describe("loading:", function () { describe("loading:", function () {
let parentDiv; let httpBackend: MockHttpBackend;
let httpBackend;
// an Object simulating the window.location // an Object simulating the window.location
let windowLocation: Location | undefined; let windowLocation: Location | undefined;
// the mounted MatrixChat // the mounted MatrixChat
let matrixChat: RenderResult<any, any, any> | undefined; let matrixChat: RenderResult | undefined;
// a promise which resolves when the MatrixChat calls onTokenLoginCompleted // a promise which resolves when the MatrixChat calls onTokenLoginCompleted
let tokenLoginCompletePromise: Promise<void> | undefined; let tokenLoginCompletePromise: Promise<void> | undefined;
beforeEach(function () { beforeEach(function () {
httpBackend = new MockHttpBackend(); httpBackend = new MockHttpBackend();
// @ts-ignore
window.fetch = httpBackend.fetchFn; window.fetch = httpBackend.fetchFn;
parentDiv = document.createElement("div");
// uncomment this to actually add the div to the UI, to help with
// debugging (but slow things down)
// document.body.appendChild(parentDiv);
windowLocation = undefined; windowLocation = undefined;
matrixChat = undefined; matrixChat = undefined;
@ -80,8 +77,13 @@ describe("loading:", function () {
* TODO: it would be nice to factor some of this stuff out of index.js so * TODO: it would be nice to factor some of this stuff out of index.js so
* that we can test it rather than our own implementation of it. * that we can test it rather than our own implementation of it.
*/ */
function loadApp(opts?): void { function loadApp(
opts = opts || {}; opts: {
queryString?: string;
uriFragment?: string;
config?: IConfigOptions;
} = {},
): void {
const queryString = opts.queryString || ""; const queryString = opts.queryString || "";
const uriFragment = opts.uriFragment || ""; const uriFragment = opts.uriFragment || "";
@ -93,7 +95,7 @@ describe("loading:", function () {
}, },
} as Location; } as Location;
function onNewScreen(screen): void { function onNewScreen(screen: string): void {
console.log(Date.now() + " newscreen " + screen); console.log(Date.now() + " newscreen " + screen);
const hash = "#/" + screen; const hash = "#/" + screen;
windowLocation!.hash = hash; windowLocation!.hash = hash;
@ -102,7 +104,7 @@ describe("loading:", function () {
// Parse the given window.location and return parameters that can be used when calling // Parse the given window.location and return parameters that can be used when calling
// MatrixChat.showScreen(screen, params) // MatrixChat.showScreen(screen, params)
function getScreenFromLocation(location): { screen: string; params: QueryDict } { function getScreenFromLocation(location: Location): { screen: string; params: QueryDict } {
const fragparts = parseQsFromFragment(location); const fragparts = parseQsFromFragment(location);
return { return {
screen: fragparts.location.substring(1), screen: fragparts.location.substring(1),
@ -112,22 +114,20 @@ describe("loading:", function () {
const fragParts = parseQsFromFragment(windowLocation); const fragParts = parseQsFromFragment(windowLocation);
const config = Object.assign( const config = {
{ default_hs_url: DEFAULT_HS_URL,
default_hs_url: DEFAULT_HS_URL, default_is_url: DEFAULT_IS_URL,
default_is_url: DEFAULT_IS_URL, validated_server_config: {
validated_server_config: { hsUrl: DEFAULT_HS_URL,
hsUrl: DEFAULT_HS_URL, hsName: "TEST_ENVIRONMENT",
hsName: "TEST_ENVIRONMENT", hsNameIsDifferent: false, // yes, we lie
hsNameIsDifferent: false, // yes, we lie isUrl: DEFAULT_IS_URL,
isUrl: DEFAULT_IS_URL, } as ValidatedServerConfig,
} as ValidatedServerConfig, embedded_pages: {
embeddedPages: { home_url: "data:text/html;charset=utf-8;base64,PGh0bWw+PC9odG1sPg==",
homeUrl: "data:text/html;charset=utf-8;base64,PGh0bWw+PC9odG1sPg==",
},
}, },
opts.config || {}, ...(opts.config ?? {}),
); } as IConfigOptions;
PlatformPeg.set(new WebPlatform()); PlatformPeg.set(new WebPlatform());
@ -137,18 +137,17 @@ describe("loading:", function () {
matrixChat = render( matrixChat = render(
<MatrixChat <MatrixChat
onNewScreen={onNewScreen} onNewScreen={onNewScreen}
config={config} config={config!}
serverConfig={config.validated_server_config} serverConfig={config.validated_server_config}
realQueryParams={params} realQueryParams={params}
startingFragmentQueryParams={fragParts.params} startingFragmentQueryParams={fragParts.params}
enableGuest={true} enableGuest={true}
onTokenLoginCompleted={resolve} onTokenLoginCompleted={resolve}
initialScreenAfterLogin={getScreenFromLocation(windowLocation)} initialScreenAfterLogin={getScreenFromLocation(windowLocation!)}
makeRegistrationUrl={(): string => { makeRegistrationUrl={(): string => {
throw new Error("Not implemented"); throw new Error("Not implemented");
}} }}
/>, />,
parentDiv,
); );
}); });
} }
@ -157,15 +156,15 @@ describe("loading:", function () {
// http requests until we do. // http requests until we do.
// //
// returns a promise resolving to the received request // returns a promise resolving to the received request
async function expectAndAwaitSync(opts?): Promise<any> { async function expectAndAwaitSync(opts?: { isGuest?: boolean }): Promise<any> {
let syncRequest = null; let syncRequest: (typeof MockHttpBackend.prototype.requests)[number] | null = null;
httpBackend.when("GET", "/_matrix/client/versions").respond(200, { httpBackend.when("GET", "/_matrix/client/versions").respond(200, {
versions: ["r0.3.0"], versions: ["r0.3.0"],
unstable_features: { unstable_features: {
"m.lazy_load_members": true, "m.lazy_load_members": true,
}, },
}); });
const isGuest = opts && opts.isGuest; const isGuest = opts?.isGuest;
if (!isGuest) { if (!isGuest) {
// the call to create the LL filter // the call to create the LL filter
httpBackend.when("POST", "/filter").respond(200, { filter_id: "llfid" }); httpBackend.when("POST", "/filter").respond(200, { filter_id: "llfid" });
@ -183,7 +182,7 @@ describe("loading:", function () {
if (syncRequest) { if (syncRequest) {
return syncRequest; return syncRequest;
} }
await httpBackend.flush(); await httpBackend.flush(undefined);
} }
throw new Error("Gave up waiting for /sync"); throw new Error("Gave up waiting for /sync");
} }
@ -201,11 +200,11 @@ describe("loading:", function () {
httpBackend httpBackend
.when("POST", "/register") .when("POST", "/register")
.check(function (req) { .check(function (req) {
expect(req.queryParams.kind).toEqual("guest"); expect(req.queryParams?.kind).toEqual("guest");
}) })
.respond(403, "Guest access is disabled"); .respond(403, "Guest access is disabled");
return httpBackend.flush(); return httpBackend.flush(undefined);
}) })
.then(() => { .then(() => {
// Wait for another trip around the event loop for the UI to update // Wait for another trip around the event loop for the UI to update
@ -234,11 +233,11 @@ describe("loading:", function () {
httpBackend httpBackend
.when("POST", "/register") .when("POST", "/register")
.check(function (req) { .check(function (req) {
expect(req.queryParams.kind).toEqual("guest"); expect(req.queryParams?.kind).toEqual("guest");
}) })
.respond(403, "Guest access is disabled"); .respond(403, "Guest access is disabled");
return httpBackend.flush(); return httpBackend.flush(undefined);
}) })
.then(() => { .then(() => {
// Wait for another trip around the event loop for the UI to update // Wait for another trip around the event loop for the UI to update
@ -403,14 +402,14 @@ describe("loading:", function () {
httpBackend httpBackend
.when("POST", "/register") .when("POST", "/register")
.check(function (req) { .check(function (req) {
expect(req.queryParams.kind).toEqual("guest"); expect(req.queryParams?.kind).toEqual("guest");
}) })
.respond(200, { .respond(200, {
user_id: "@guest:localhost", user_id: "@guest:localhost",
access_token: "secret_token", access_token: "secret_token",
}); });
return httpBackend.flush(); return httpBackend.flush(undefined);
}) })
.then(() => { .then(() => {
return awaitLoggedIn(matrixChat!); return awaitLoggedIn(matrixChat!);
@ -440,14 +439,14 @@ describe("loading:", function () {
.when("POST", "/register") .when("POST", "/register")
.check(function (req) { .check(function (req) {
expect(req.path.startsWith(DEFAULT_HS_URL)).toBe(true); expect(req.path.startsWith(DEFAULT_HS_URL)).toBe(true);
expect(req.queryParams.kind).toEqual("guest"); expect(req.queryParams?.kind).toEqual("guest");
}) })
.respond(200, { .respond(200, {
user_id: "@guest:localhost", user_id: "@guest:localhost",
access_token: "secret_token", access_token: "secret_token",
}); });
return httpBackend.flush(); return httpBackend.flush(undefined);
}) })
.then(() => { .then(() => {
return awaitLoggedIn(matrixChat!); return awaitLoggedIn(matrixChat!);
@ -480,14 +479,14 @@ describe("loading:", function () {
httpBackend httpBackend
.when("POST", "/register") .when("POST", "/register")
.check(function (req) { .check(function (req) {
expect(req.queryParams.kind).toEqual("guest"); expect(req.queryParams?.kind).toEqual("guest");
}) })
.respond(200, { .respond(200, {
user_id: "@guest:localhost", user_id: "@guest:localhost",
access_token: "secret_token", access_token: "secret_token",
}); });
return httpBackend.flush(); return httpBackend.flush(undefined);
}) })
.then(() => { .then(() => {
return awaitLoggedIn(matrixChat!); return awaitLoggedIn(matrixChat!);
@ -513,7 +512,7 @@ describe("loading:", function () {
httpBackend httpBackend
.when("POST", "/register") .when("POST", "/register")
.check(function (req) { .check(function (req) {
expect(req.queryParams.kind).toEqual("guest"); expect(req.queryParams?.kind).toEqual("guest");
}) })
.respond(200, { .respond(200, {
user_id: "@guest:localhost", user_id: "@guest:localhost",
@ -521,7 +520,7 @@ describe("loading:", function () {
}); });
return httpBackend return httpBackend
.flush() .flush(undefined)
.then(() => { .then(() => {
return awaitLoggedIn(matrixChat!); return awaitLoggedIn(matrixChat!);
}) })
@ -584,7 +583,7 @@ describe("loading:", function () {
access_token: "access_token", access_token: "access_token",
}); });
return httpBackend.flush(); return httpBackend.flush(undefined);
}) })
.then(() => { .then(() => {
// at this point, MatrixChat should fire onTokenLoginCompleted, which // at this point, MatrixChat should fire onTokenLoginCompleted, which
@ -607,11 +606,11 @@ describe("loading:", function () {
// check that we have a Login component, send a 'user:pass' login, // check that we have a Login component, send a 'user:pass' login,
// and await the HTTP requests. // and await the HTTP requests.
async function completeLogin(matrixChat: RenderResult<any, any, any>): Promise<void> { async function completeLogin(matrixChat: RenderResult): Promise<void> {
// When we switch to the login component, it'll hit the login endpoint // When we switch to the login component, it'll hit the login endpoint
// for proof of life and to get flows. We'll only give it one option. // for proof of life and to get flows. We'll only give it one option.
httpBackend.when("GET", "/login").respond(200, { flows: [{ type: "m.login.password" }] }); httpBackend.when("GET", "/login").respond(200, { flows: [{ type: "m.login.password" }] });
httpBackend.flush(); // We already would have tried the GET /login request httpBackend.flush(undefined); // We already would have tried the GET /login request
// Give the component some time to finish processing the login flows before // Give the component some time to finish processing the login flows before
// continuing. // continuing.
@ -635,7 +634,7 @@ describe("loading:", function () {
fireEvent.click(screen.getByText("Sign in", { selector: ".mx_Login_submit" })); fireEvent.click(screen.getByText("Sign in", { selector: ".mx_Login_submit" }));
return httpBackend return httpBackend
.flush() .flush(undefined)
.then(() => { .then(() => {
// Wait for another trip around the event loop for the UI to update // Wait for another trip around the event loop for the UI to update
return sleep(1); return sleep(1);
@ -656,11 +655,11 @@ async function assertAtLoadingSpinner(): Promise<void> {
await screen.findByRole("progressbar"); await screen.findByRole("progressbar");
} }
async function awaitLoggedIn(matrixChat: RenderResult<any, any, any>): Promise<void> { async function awaitLoggedIn(matrixChat: RenderResult): Promise<void> {
if (matrixChat.container.querySelector(".mx_MatrixChat_wrapper")) return; // already logged in if (matrixChat.container.querySelector(".mx_MatrixChat_wrapper")) return; // already logged in
return new Promise((resolve) => { return new Promise((resolve) => {
const onAction = ({ action }): void => { const onAction = ({ action }: ActionPayload): void => {
if (action !== "on_logged_in") { if (action !== "on_logged_in") {
return; return;
} }
@ -673,19 +672,19 @@ async function awaitLoggedIn(matrixChat: RenderResult<any, any, any>): Promise<v
}); });
} }
async function awaitRoomView(matrixChat?: RenderResult<any, any, any>): Promise<void> { async function awaitRoomView(matrixChat?: RenderResult): Promise<void> {
await waitFor(() => matrixChat?.container.querySelector(".mx_RoomView")); await waitFor(() => matrixChat?.container.querySelector(".mx_RoomView"));
} }
async function awaitLoginComponent(matrixChat?: RenderResult<any, any, any>): Promise<void> { async function awaitLoginComponent(matrixChat?: RenderResult): Promise<void> {
await waitFor(() => matrixChat?.container.querySelector(".mx_AuthPage")); await waitFor(() => matrixChat?.container.querySelector(".mx_AuthPage"));
} }
async function awaitWelcomeComponent(matrixChat?: RenderResult<any, any, any>): Promise<void> { async function awaitWelcomeComponent(matrixChat?: RenderResult): Promise<void> {
await waitFor(() => matrixChat?.container.querySelector(".mx_Welcome")); await waitFor(() => matrixChat?.container.querySelector(".mx_Welcome"));
} }
function moveFromWelcomeToLogin(matrixChat?: RenderResult<any, any, any>): Promise<void> { function moveFromWelcomeToLogin(matrixChat?: RenderResult): Promise<void> {
dis.dispatch({ action: "start_login" }); dis.dispatch({ action: "start_login" });
return awaitLoginComponent(matrixChat); return awaitLoginComponent(matrixChat);
} }

View File

@ -29,7 +29,7 @@ describe("Favicon", () => {
it("should create a link element if one doesn't yet exist", () => { it("should create a link element if one doesn't yet exist", () => {
const favicon = new Favicon(); const favicon = new Favicon();
expect(favicon).toBeTruthy(); expect(favicon).toBeTruthy();
const link = window.document.querySelector("link"); const link = window.document.querySelector("link")!;
expect(link.rel).toContain("icon"); expect(link.rel).toContain("icon");
}); });

View File

@ -21,7 +21,6 @@ import { getVectorConfig } from "../../../src/vector/getconfig";
fetchMock.config.overwriteRoutes = true; fetchMock.config.overwriteRoutes = true;
describe("getVectorConfig()", () => { describe("getVectorConfig()", () => {
const prevDocumentDomain = document.domain;
const elementDomain = "app.element.io"; const elementDomain = "app.element.io";
const now = 1234567890; const now = 1234567890;
const specificConfig = { const specificConfig = {
@ -32,7 +31,10 @@ describe("getVectorConfig()", () => {
}; };
beforeEach(() => { beforeEach(() => {
document.domain = elementDomain; Object.defineProperty(window, "location", {
value: { href: `https://${elementDomain}`, hostname: elementDomain },
writable: true,
});
// stable value for cachebuster // stable value for cachebuster
jest.spyOn(Date, "now").mockReturnValue(now); jest.spyOn(Date, "now").mockReturnValue(now);
@ -41,7 +43,6 @@ describe("getVectorConfig()", () => {
}); });
afterAll(() => { afterAll(() => {
document.domain = prevDocumentDomain;
jest.spyOn(Date, "now").mockRestore(); jest.spyOn(Date, "now").mockRestore();
}); });
@ -107,6 +108,6 @@ describe("getVectorConfig()", () => {
// We can't assert it'll be a SyntaxError as node-fetch behaves differently // We can't assert it'll be a SyntaxError as node-fetch behaves differently
// https://github.com/wheresrhys/fetch-mock/issues/270 // https://github.com/wheresrhys/fetch-mock/issues/270
await expect(getVectorConfig()).rejects.toThrow("Unexpected token } in JSON at position 19"); await expect(getVectorConfig()).rejects.toThrow("in JSON at position 19");
}); });
}); });

View File

@ -47,8 +47,7 @@ describe("ElectronPlatform", () => {
beforeEach(() => { beforeEach(() => {
window.electron = mockElectron; window.electron = mockElectron;
jest.clearAllMocks(); jest.clearAllMocks();
delete window.navigator; Object.defineProperty(window, "navigator", { value: { userAgent: defaultUserAgent }, writable: true });
window.navigator = { userAgent: defaultUserAgent } as unknown as Navigator;
}); });
const getElectronEventHandlerCall = (eventType: string): [type: string, handler: Function] | undefined => const getElectronEventHandlerCall = (eventType: string): [type: string, handler: Function] | undefined =>
@ -56,7 +55,7 @@ describe("ElectronPlatform", () => {
it("flushes rageshake before quitting", () => { it("flushes rageshake before quitting", () => {
new ElectronPlatform(); new ElectronPlatform();
const [event, handler] = getElectronEventHandlerCall("before-quit"); const [event, handler] = getElectronEventHandlerCall("before-quit")!;
// correct event bound // correct event bound
expect(event).toBeTruthy(); expect(event).toBeTruthy();
@ -68,7 +67,7 @@ describe("ElectronPlatform", () => {
it("dispatches view settings action on preferences event", () => { it("dispatches view settings action on preferences event", () => {
new ElectronPlatform(); new ElectronPlatform();
const [event, handler] = getElectronEventHandlerCall("preferences"); const [event, handler] = getElectronEventHandlerCall("preferences")!;
// correct event bound // correct event bound
expect(event).toBeTruthy(); expect(event).toBeTruthy();
@ -80,7 +79,7 @@ describe("ElectronPlatform", () => {
describe("updates", () => { describe("updates", () => {
it("dispatches on check updates action", () => { it("dispatches on check updates action", () => {
new ElectronPlatform(); new ElectronPlatform();
const [event, handler] = getElectronEventHandlerCall("check_updates"); const [event, handler] = getElectronEventHandlerCall("check_updates")!;
// correct event bound // correct event bound
expect(event).toBeTruthy(); expect(event).toBeTruthy();
@ -93,7 +92,7 @@ describe("ElectronPlatform", () => {
it("dispatches on check updates action when update not available", () => { it("dispatches on check updates action when update not available", () => {
new ElectronPlatform(); new ElectronPlatform();
const [, handler] = getElectronEventHandlerCall("check_updates"); const [, handler] = getElectronEventHandlerCall("check_updates")!;
handler({}, false); handler({}, false);
expect(dispatchSpy).toHaveBeenCalledWith({ expect(dispatchSpy).toHaveBeenCalledWith({
@ -138,8 +137,7 @@ describe("ElectronPlatform", () => {
["Mozilla/5.0 (X11; SunOS i686; rv:21.0) Gecko/20100101 Firefox/21.0", "Element Desktop: SunOS"], ["Mozilla/5.0 (X11; SunOS i686; rv:21.0) Gecko/20100101 Firefox/21.0", "Element Desktop: SunOS"],
["custom user agent", "Element Desktop: Unknown"], ["custom user agent", "Element Desktop: Unknown"],
])("%s = %s", (userAgent, result) => { ])("%s = %s", (userAgent, result) => {
delete window.navigator; Object.defineProperty(window, "navigator", { value: { userAgent }, writable: true });
window.navigator = { userAgent } as unknown as Navigator;
const platform = new ElectronPlatform(); const platform = new ElectronPlatform();
expect(platform.getDefaultDeviceDisplayName()).toEqual(result); expect(platform.getDefaultDeviceDisplayName()).toEqual(result);
}); });

View File

@ -33,7 +33,6 @@ describe("WebPlatform", () => {
}); });
it("registers service worker", () => { it("registers service worker", () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - mocking readonly object // @ts-ignore - mocking readonly object
navigator.serviceWorker = { register: jest.fn() }; navigator.serviceWorker = { register: jest.fn() };
new WebPlatform(); new WebPlatform();
@ -41,10 +40,7 @@ describe("WebPlatform", () => {
}); });
it("should call reload on window location object", () => { it("should call reload on window location object", () => {
delete window.location; Object.defineProperty(window, "location", { value: { reload: jest.fn() }, writable: true });
window.location = {
reload: jest.fn(),
} as unknown as Location;
const platform = new WebPlatform(); const platform = new WebPlatform();
expect(window.location.reload).not.toHaveBeenCalled(); expect(window.location.reload).not.toHaveBeenCalled();
@ -53,10 +49,7 @@ describe("WebPlatform", () => {
}); });
it("should call reload to install update", () => { it("should call reload to install update", () => {
delete window.location; Object.defineProperty(window, "location", { value: { reload: jest.fn() }, writable: true });
window.location = {
reload: jest.fn(),
} as unknown as Location;
const platform = new WebPlatform(); const platform = new WebPlatform();
expect(window.location.reload).not.toHaveBeenCalled(); expect(window.location.reload).not.toHaveBeenCalled();
@ -73,10 +66,8 @@ describe("WebPlatform", () => {
"develop.element.io: Chrome on macOS", "develop.element.io: Chrome on macOS",
], ],
])("%s & %s = %s", (url, userAgent, result) => { ])("%s & %s = %s", (url, userAgent, result) => {
delete window.navigator; Object.defineProperty(window, "navigator", { value: { userAgent }, writable: true });
window.navigator = { userAgent } as unknown as Navigator; Object.defineProperty(window, "location", { value: { href: url }, writable: true });
delete window.location;
window.location = { href: url } as unknown as Location;
const platform = new WebPlatform(); const platform = new WebPlatform();
expect(platform.getDefaultDeviceDisplayName()).toEqual(result); expect(platform.getDefaultDeviceDisplayName()).toEqual(result);
}); });
@ -88,14 +79,12 @@ describe("WebPlatform", () => {
permission: "notGranted", permission: "notGranted",
}; };
beforeEach(() => { beforeEach(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
window.Notification = mockNotification; window.Notification = mockNotification;
mockNotification.permission = "notGranted"; mockNotification.permission = "notGranted";
}); });
it("supportsNotifications returns false when platform does not support notifications", () => { it("supportsNotifications returns false when platform does not support notifications", () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
window.Notification = undefined; window.Notification = undefined;
expect(new WebPlatform().supportsNotifications()).toBe(false); expect(new WebPlatform().supportsNotifications()).toBe(false);
@ -132,7 +121,8 @@ describe("WebPlatform", () => {
}); });
afterAll(() => { afterAll(() => {
process.env.VERSION = envVersion; // @ts-ignore
WebPlatform.VERSION = envVersion;
}); });
it("should return true from canSelfUpdate()", async () => { it("should return true from canSelfUpdate()", async () => {
@ -142,18 +132,21 @@ describe("WebPlatform", () => {
}); });
it("getAppVersion returns normalized app version", async () => { it("getAppVersion returns normalized app version", async () => {
process.env.VERSION = prodVersion; // @ts-ignore
WebPlatform.VERSION = prodVersion;
const platform = new WebPlatform(); const platform = new WebPlatform();
const version = await platform.getAppVersion(); const version = await platform.getAppVersion();
expect(version).toEqual(prodVersion); expect(version).toEqual(prodVersion);
process.env.VERSION = `v${prodVersion}`; // @ts-ignore
WebPlatform.VERSION = `v${prodVersion}`;
const version2 = await platform.getAppVersion(); const version2 = await platform.getAppVersion();
// v prefix removed // v prefix removed
expect(version2).toEqual(prodVersion); expect(version2).toEqual(prodVersion);
process.env.VERSION = `version not like semver`; // @ts-ignore
WebPlatform.VERSION = `version not like semver`;
const notSemverVersion = await platform.getAppVersion(); const notSemverVersion = await platform.getAppVersion();
expect(notSemverVersion).toEqual(`version not like semver`); expect(notSemverVersion).toEqual(`version not like semver`);
}); });
@ -163,7 +156,8 @@ describe("WebPlatform", () => {
"should return not available and call showNoUpdate when current version " + "should return not available and call showNoUpdate when current version " +
"matches most recent version", "matches most recent version",
async () => { async () => {
process.env.VERSION = prodVersion; // @ts-ignore
WebPlatform.VERSION = prodVersion;
fetchMock.getOnce("/version", prodVersion); fetchMock.getOnce("/version", prodVersion);
const platform = new WebPlatform(); const platform = new WebPlatform();
@ -178,7 +172,8 @@ describe("WebPlatform", () => {
); );
it("should strip v prefix from versions before comparing", async () => { it("should strip v prefix from versions before comparing", async () => {
process.env.VERSION = prodVersion; // @ts-ignore
WebPlatform.VERSION = prodVersion;
fetchMock.getOnce("/version", `v${prodVersion}`); fetchMock.getOnce("/version", `v${prodVersion}`);
const platform = new WebPlatform(); const platform = new WebPlatform();
@ -195,7 +190,8 @@ describe("WebPlatform", () => {
it( it(
"should return ready and call showUpdate when current version " + "differs from most recent version", "should return ready and call showUpdate when current version " + "differs from most recent version",
async () => { async () => {
process.env.VERSION = "0.0.0"; // old version // @ts-ignore
WebPlatform.VERSION = "0.0.0"; // old version
fetchMock.getOnce("/version", prodVersion); fetchMock.getOnce("/version", prodVersion);
const platform = new WebPlatform(); const platform = new WebPlatform();
@ -210,7 +206,8 @@ describe("WebPlatform", () => {
); );
it("should return ready without showing update when user registered in last 24", async () => { it("should return ready without showing update when user registered in last 24", async () => {
process.env.VERSION = "0.0.0"; // old version // @ts-ignore
WebPlatform.VERSION = "0.0.0"; // old version
jest.spyOn(MatrixClientPeg, "userRegisteredWithinLastHours").mockReturnValue(true); jest.spyOn(MatrixClientPeg, "userRegisteredWithinLastHours").mockReturnValue(true);
fetchMock.getOnce("/version", prodVersion); fetchMock.getOnce("/version", prodVersion);
const platform = new WebPlatform(); const platform = new WebPlatform();

View File

@ -18,24 +18,28 @@ import { onNewScreen } from "../../../src/vector/routing";
describe("onNewScreen", () => { describe("onNewScreen", () => {
it("should replace history if stripping via fields", () => { it("should replace history if stripping via fields", () => {
delete window.location; Object.defineProperty(window, "location", {
window.location = { value: {
hash: "#/room/!room:server?via=abc", hash: "#/room/!room:server?via=abc",
replace: jest.fn(), replace: jest.fn(),
assign: jest.fn(), assign: jest.fn(),
} as unknown as Location; },
writable: true,
});
onNewScreen("room/!room:server"); onNewScreen("room/!room:server");
expect(window.location.assign).not.toHaveBeenCalled(); expect(window.location.assign).not.toHaveBeenCalled();
expect(window.location.replace).toHaveBeenCalled(); expect(window.location.replace).toHaveBeenCalled();
}); });
it("should not replace history if changing rooms", () => { it("should not replace history if changing rooms", () => {
delete window.location; Object.defineProperty(window, "location", {
window.location = { value: {
hash: "#/room/!room1:server?via=abc", hash: "#/room/!room1:server?via=abc",
replace: jest.fn(), replace: jest.fn(),
assign: jest.fn(), assign: jest.fn(),
} as unknown as Location; },
writable: true,
});
onNewScreen("room/!room2:server"); onNewScreen("room/!room2:server");
expect(window.location.assign).toHaveBeenCalled(); expect(window.location.assign).toHaveBeenCalled();
expect(window.location.replace).not.toHaveBeenCalled(); expect(window.location.replace).not.toHaveBeenCalled();

View File

@ -17,7 +17,6 @@ limitations under the License.
import { parseQsFromFragment, parseQs } from "../../../src/vector/url_utils"; import { parseQsFromFragment, parseQs } from "../../../src/vector/url_utils";
describe("url_utils.ts", function () { describe("url_utils.ts", function () {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
const location: Location = { const location: Location = {
hash: "", hash: "",

View File

@ -1544,9 +1544,9 @@
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-wysiwyg/-/matrix-wysiwyg-2.0.0.tgz#913eb5faa5d43c7a4ee9bda68de1aa1dcc49a11d" resolved "https://registry.yarnpkg.com/@matrix-org/matrix-wysiwyg/-/matrix-wysiwyg-2.0.0.tgz#913eb5faa5d43c7a4ee9bda68de1aa1dcc49a11d"
integrity sha512-xRYFwsf6Jx7KTCpwU91mVhPA8q/c+oOVyK98NnexyK/IcQS7BMFAns5GZX9b6ZEy38u30KoxeN6mxvxi+ysQgg== integrity sha512-xRYFwsf6Jx7KTCpwU91mVhPA8q/c+oOVyK98NnexyK/IcQS7BMFAns5GZX9b6ZEy38u30KoxeN6mxvxi+ysQgg==
"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.12.tgz": "@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz":
version "3.2.12" version "3.2.14"
resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.12.tgz#0bce3c86f9d36a4984d3c3e07df1c3fb4c679bd9" resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz#acd96c00a881d0f462e1f97a56c73742c8dbc984"
"@matrix-org/react-sdk-module-api@^0.0.4": "@matrix-org/react-sdk-module-api@^0.0.4":
version "0.0.4" version "0.0.4"
@ -2104,6 +2104,11 @@
expect "^29.0.0" expect "^29.0.0"
pretty-format "^29.0.0" pretty-format "^29.0.0"
"@types/jitsi-meet@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@types/jitsi-meet/-/jitsi-meet-2.0.2.tgz#4670e6dd47f2762cda5af53b73ab0a6e39ec0205"
integrity sha512-rnb5znCdZs7T2VgA16wyu5UHIbq+WR2HH233GPkS6HQ9m7Sh5jiXChZ41jo0tCTtNuSCziPIb5sI+6OPKK8h+Q==
"@types/jsdom@^20.0.0": "@types/jsdom@^20.0.0":
version "20.0.1" version "20.0.1"
resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-20.0.1.tgz#07c14bc19bd2f918c1929541cdaacae894744808" resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-20.0.1.tgz#07c14bc19bd2f918c1929541cdaacae894744808"