mirror of https://github.com/vector-im/riot-web
Flatten Vector-override components (#28346)
* Flatten Vector-override components Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Improve coverage Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Ie Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * delint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>pull/28401/head
parent
a355292a7f
commit
9d79a934bf
|
@ -1,5 +1 @@
|
||||||
{
|
{}
|
||||||
"src/components/views/auth/AuthFooter.tsx": "src/components/views/auth/VectorAuthFooter.tsx",
|
|
||||||
"src/components/views/auth/AuthHeaderLogo.tsx": "src/components/views/auth/VectorAuthHeaderLogo.tsx",
|
|
||||||
"src/components/views/auth/AuthPage.tsx": "src/components/views/auth/VectorAuthPage.tsx"
|
|
||||||
}
|
|
||||||
|
|
|
@ -31,6 +31,8 @@ import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
|
||||||
import { IConfigOptions } from "./IConfigOptions";
|
import { IConfigOptions } from "./IConfigOptions";
|
||||||
import SdkConfig from "./SdkConfig";
|
import SdkConfig from "./SdkConfig";
|
||||||
import { buildAndEncodePickleKey, encryptPickleKey } from "./utils/tokens/pickling";
|
import { buildAndEncodePickleKey, encryptPickleKey } from "./utils/tokens/pickling";
|
||||||
|
import Favicon from "./favicon.ts";
|
||||||
|
import { getVectorConfig } from "./vector/getconfig.ts";
|
||||||
|
|
||||||
export const SSO_HOMESERVER_URL_KEY = "mx_sso_hs_url";
|
export const SSO_HOMESERVER_URL_KEY = "mx_sso_hs_url";
|
||||||
export const SSO_ID_SERVER_URL_KEY = "mx_sso_is_url";
|
export const SSO_ID_SERVER_URL_KEY = "mx_sso_is_url";
|
||||||
|
@ -66,14 +68,20 @@ const UPDATE_DEFER_KEY = "mx_defer_update";
|
||||||
export default abstract class BasePlatform {
|
export default abstract class BasePlatform {
|
||||||
protected notificationCount = 0;
|
protected notificationCount = 0;
|
||||||
protected errorDidOccur = false;
|
protected errorDidOccur = false;
|
||||||
|
protected _favicon?: Favicon;
|
||||||
|
|
||||||
protected constructor() {
|
protected constructor() {
|
||||||
dis.register(this.onAction);
|
dis.register(this.onAction);
|
||||||
this.startUpdateCheck = this.startUpdateCheck.bind(this);
|
this.startUpdateCheck = this.startUpdateCheck.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract getConfig(): Promise<IConfigOptions | undefined>;
|
public async getConfig(): Promise<IConfigOptions | undefined> {
|
||||||
|
return getVectorConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a sensible default display name for the device Element is running on
|
||||||
|
*/
|
||||||
public abstract getDefaultDeviceDisplayName(): string;
|
public abstract getDefaultDeviceDisplayName(): string;
|
||||||
|
|
||||||
protected onAction = (payload: ActionPayload): void => {
|
protected onAction = (payload: ActionPayload): void => {
|
||||||
|
@ -89,11 +97,15 @@ export default abstract class BasePlatform {
|
||||||
public abstract getHumanReadableName(): string;
|
public abstract getHumanReadableName(): string;
|
||||||
|
|
||||||
public setNotificationCount(count: number): void {
|
public setNotificationCount(count: number): void {
|
||||||
|
if (this.notificationCount === count) return;
|
||||||
this.notificationCount = count;
|
this.notificationCount = count;
|
||||||
|
this.updateFavicon();
|
||||||
}
|
}
|
||||||
|
|
||||||
public setErrorStatus(errorDidOccur: boolean): void {
|
public setErrorStatus(errorDidOccur: boolean): void {
|
||||||
|
if (this.errorDidOccur === errorDidOccur) return;
|
||||||
this.errorDidOccur = errorDidOccur;
|
this.errorDidOccur = errorDidOccur;
|
||||||
|
this.updateFavicon();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -456,4 +468,34 @@ export default abstract class BasePlatform {
|
||||||
url.hash = "";
|
url.hash = "";
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delay creating the `Favicon` instance until first use (on the first notification) as
|
||||||
|
* it uses canvas, which can trigger a permission prompt in Firefox's resist fingerprinting mode.
|
||||||
|
* See https://github.com/element-hq/element-web/issues/9605.
|
||||||
|
*/
|
||||||
|
public get favicon(): Favicon {
|
||||||
|
if (this._favicon) {
|
||||||
|
return this._favicon;
|
||||||
|
}
|
||||||
|
this._favicon = new Favicon();
|
||||||
|
return this._favicon;
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateFavicon(): void {
|
||||||
|
let bgColor = "#d00";
|
||||||
|
let notif: string | number = this.notificationCount;
|
||||||
|
|
||||||
|
if (this.errorDidOccur) {
|
||||||
|
notif = notif || "×";
|
||||||
|
bgColor = "#f00";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.favicon.badge(notif, { bgColor });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin update polling, if applicable
|
||||||
|
*/
|
||||||
|
public startUpdater(): void {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,18 +7,36 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React, { ReactElement } from "react";
|
||||||
|
|
||||||
|
import SdkConfig from "../../../SdkConfig";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
|
|
||||||
export default class AuthFooter extends React.Component {
|
const AuthFooter = (): ReactElement => {
|
||||||
public render(): React.ReactNode {
|
const brandingConfig = SdkConfig.getObject("branding");
|
||||||
|
const links = brandingConfig?.get("auth_footer_links") ?? [
|
||||||
|
{ text: "Blog", url: "https://element.io/blog" },
|
||||||
|
{ text: "Twitter", url: "https://twitter.com/element_hq" },
|
||||||
|
{ text: "GitHub", url: "https://github.com/element-hq/element-web" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const authFooterLinks: JSX.Element[] = [];
|
||||||
|
for (const linkEntry of links) {
|
||||||
|
authFooterLinks.push(
|
||||||
|
<a href={linkEntry.url} key={linkEntry.text} target="_blank" rel="noreferrer noopener">
|
||||||
|
{linkEntry.text}
|
||||||
|
</a>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="mx_AuthFooter" role="contentinfo">
|
<footer className="mx_AuthFooter" role="contentinfo">
|
||||||
|
{authFooterLinks}
|
||||||
<a href="https://matrix.org" target="_blank" rel="noreferrer noopener">
|
<a href="https://matrix.org" target="_blank" rel="noreferrer noopener">
|
||||||
{_t("auth|footer_powered_by_matrix")}
|
{_t("powered_by_matrix")}
|
||||||
</a>
|
</a>
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
export default AuthFooter;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019-2024 New Vector Ltd.
|
Copyright 2019-2024 New Vector Ltd.
|
||||||
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
@ -7,8 +8,17 @@ Please see LICENSE files in the repository root for full details.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import SdkConfig from "../../../SdkConfig";
|
||||||
|
|
||||||
export default class AuthHeaderLogo extends React.PureComponent {
|
export default class AuthHeaderLogo extends React.PureComponent {
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactElement {
|
||||||
return <aside className="mx_AuthHeaderLogo">Matrix</aside>;
|
const brandingConfig = SdkConfig.getObject("branding");
|
||||||
|
const logoUrl = brandingConfig?.get("auth_header_logo_url") ?? "themes/element/img/logos/element-logo.svg";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<aside className="mx_AuthHeaderLogo">
|
||||||
|
<img src={logoUrl} alt="Element" />
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,15 +7,69 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { ReactNode } from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import SdkConfig from "../../../SdkConfig";
|
||||||
import AuthFooter from "./AuthFooter";
|
import AuthFooter from "./AuthFooter";
|
||||||
|
|
||||||
export default class AuthPage extends React.PureComponent<{ children: ReactNode }> {
|
export default class AuthPage extends React.PureComponent<React.PropsWithChildren> {
|
||||||
public render(): React.ReactNode {
|
private static welcomeBackgroundUrl?: string;
|
||||||
|
|
||||||
|
// cache the url as a static to prevent it changing without refreshing
|
||||||
|
private static getWelcomeBackgroundUrl(): string {
|
||||||
|
if (AuthPage.welcomeBackgroundUrl) return AuthPage.welcomeBackgroundUrl;
|
||||||
|
|
||||||
|
const brandingConfig = SdkConfig.getObject("branding");
|
||||||
|
AuthPage.welcomeBackgroundUrl = "themes/element/img/backgrounds/lake.jpg";
|
||||||
|
|
||||||
|
const configuredUrl = brandingConfig?.get("welcome_background_url");
|
||||||
|
if (configuredUrl) {
|
||||||
|
if (Array.isArray(configuredUrl)) {
|
||||||
|
const index = Math.floor(Math.random() * configuredUrl.length);
|
||||||
|
AuthPage.welcomeBackgroundUrl = configuredUrl[index];
|
||||||
|
} else {
|
||||||
|
AuthPage.welcomeBackgroundUrl = configuredUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AuthPage.welcomeBackgroundUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): React.ReactElement {
|
||||||
|
const pageStyle = {
|
||||||
|
background: `center/cover fixed url(${AuthPage.getWelcomeBackgroundUrl()})`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const modalStyle: React.CSSProperties = {
|
||||||
|
position: "relative",
|
||||||
|
background: "initial",
|
||||||
|
};
|
||||||
|
|
||||||
|
const blurStyle: React.CSSProperties = {
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
filter: "blur(40px)",
|
||||||
|
background: pageStyle.background,
|
||||||
|
};
|
||||||
|
|
||||||
|
const modalContentStyle: React.CSSProperties = {
|
||||||
|
display: "flex",
|
||||||
|
zIndex: 1,
|
||||||
|
background: "rgba(255, 255, 255, 0.59)",
|
||||||
|
borderRadius: "8px",
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_AuthPage">
|
<div className="mx_AuthPage" style={pageStyle}>
|
||||||
<div className="mx_AuthPage_modal">{this.props.children}</div>
|
<div className="mx_AuthPage_modal" style={modalStyle}>
|
||||||
|
<div className="mx_AuthPage_modalBlur" style={blurStyle} />
|
||||||
|
<div className="mx_AuthPage_modalContent" style={modalContentStyle}>
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<AuthFooter />
|
<AuthFooter />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2019-2024 New Vector Ltd.
|
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
|
||||||
Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { ReactElement } from "react";
|
|
||||||
|
|
||||||
import SdkConfig from "../../../SdkConfig";
|
|
||||||
import { _t } from "../../../languageHandler";
|
|
||||||
|
|
||||||
const VectorAuthFooter = (): ReactElement => {
|
|
||||||
const brandingConfig = SdkConfig.getObject("branding");
|
|
||||||
const links = brandingConfig?.get("auth_footer_links") ?? [
|
|
||||||
{ text: "Blog", url: "https://element.io/blog" },
|
|
||||||
{ text: "Twitter", url: "https://twitter.com/element_hq" },
|
|
||||||
{ text: "GitHub", url: "https://github.com/element-hq/element-web" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const authFooterLinks: JSX.Element[] = [];
|
|
||||||
for (const linkEntry of links) {
|
|
||||||
authFooterLinks.push(
|
|
||||||
<a href={linkEntry.url} key={linkEntry.text} target="_blank" rel="noreferrer noopener">
|
|
||||||
{linkEntry.text}
|
|
||||||
</a>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<footer className="mx_AuthFooter" role="contentinfo">
|
|
||||||
{authFooterLinks}
|
|
||||||
<a href="https://matrix.org" target="_blank" rel="noreferrer noopener">
|
|
||||||
{_t("powered_by_matrix")}
|
|
||||||
</a>
|
|
||||||
</footer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default VectorAuthFooter;
|
|
|
@ -1,24 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2019-2024 New Vector Ltd.
|
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
|
||||||
Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
|
|
||||||
import SdkConfig from "../../../SdkConfig";
|
|
||||||
|
|
||||||
export default class VectorAuthHeaderLogo extends React.PureComponent {
|
|
||||||
public render(): React.ReactElement {
|
|
||||||
const brandingConfig = SdkConfig.getObject("branding");
|
|
||||||
const logoUrl = brandingConfig?.get("auth_header_logo_url") ?? "themes/element/img/logos/element-logo.svg";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<aside className="mx_AuthHeaderLogo">
|
|
||||||
<img src={logoUrl} alt="Element" />
|
|
||||||
</aside>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2019-2024 New Vector Ltd.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
|
||||||
Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
|
|
||||||
import SdkConfig from "../../../SdkConfig";
|
|
||||||
import VectorAuthFooter from "./VectorAuthFooter";
|
|
||||||
|
|
||||||
export default class VectorAuthPage extends React.PureComponent<React.PropsWithChildren> {
|
|
||||||
private static welcomeBackgroundUrl?: string;
|
|
||||||
|
|
||||||
// cache the url as a static to prevent it changing without refreshing
|
|
||||||
private static getWelcomeBackgroundUrl(): string {
|
|
||||||
if (VectorAuthPage.welcomeBackgroundUrl) return VectorAuthPage.welcomeBackgroundUrl;
|
|
||||||
|
|
||||||
const brandingConfig = SdkConfig.getObject("branding");
|
|
||||||
VectorAuthPage.welcomeBackgroundUrl = "themes/element/img/backgrounds/lake.jpg";
|
|
||||||
|
|
||||||
const configuredUrl = brandingConfig?.get("welcome_background_url");
|
|
||||||
if (configuredUrl) {
|
|
||||||
if (Array.isArray(configuredUrl)) {
|
|
||||||
const index = Math.floor(Math.random() * configuredUrl.length);
|
|
||||||
VectorAuthPage.welcomeBackgroundUrl = configuredUrl[index];
|
|
||||||
} else {
|
|
||||||
VectorAuthPage.welcomeBackgroundUrl = configuredUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return VectorAuthPage.welcomeBackgroundUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(): React.ReactElement {
|
|
||||||
const pageStyle = {
|
|
||||||
background: `center/cover fixed url(${VectorAuthPage.getWelcomeBackgroundUrl()})`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const modalStyle: React.CSSProperties = {
|
|
||||||
position: "relative",
|
|
||||||
background: "initial",
|
|
||||||
};
|
|
||||||
|
|
||||||
const blurStyle: React.CSSProperties = {
|
|
||||||
position: "absolute",
|
|
||||||
top: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
filter: "blur(40px)",
|
|
||||||
background: pageStyle.background,
|
|
||||||
};
|
|
||||||
|
|
||||||
const modalContentStyle: React.CSSProperties = {
|
|
||||||
display: "flex",
|
|
||||||
zIndex: 1,
|
|
||||||
background: "rgba(255, 255, 255, 0.59)",
|
|
||||||
borderRadius: "8px",
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mx_AuthPage" style={pageStyle}>
|
|
||||||
<div className="mx_AuthPage_modal" style={modalStyle}>
|
|
||||||
<div className="mx_AuthPage_modalBlur" style={blurStyle} />
|
|
||||||
<div className="mx_AuthPage_modalContent" style={modalContentStyle}>
|
|
||||||
{this.props.children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<VectorAuthFooter />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -209,7 +209,6 @@
|
||||||
"failed_query_registration_methods": "Unable to query for supported registration methods.",
|
"failed_query_registration_methods": "Unable to query for supported registration methods.",
|
||||||
"failed_soft_logout_auth": "Failed to re-authenticate",
|
"failed_soft_logout_auth": "Failed to re-authenticate",
|
||||||
"failed_soft_logout_homeserver": "Failed to re-authenticate due to a homeserver problem",
|
"failed_soft_logout_homeserver": "Failed to re-authenticate due to a homeserver problem",
|
||||||
"footer_powered_by_matrix": "powered by Matrix",
|
|
||||||
"forgot_password_email_invalid": "The email address doesn't appear to be valid.",
|
"forgot_password_email_invalid": "The email address doesn't appear to be valid.",
|
||||||
"forgot_password_email_required": "The email address linked to your account must be entered.",
|
"forgot_password_email_required": "The email address linked to your account must be entered.",
|
||||||
"forgot_password_prompt": "Forgotten your password?",
|
"forgot_password_prompt": "Forgotten your password?",
|
||||||
|
@ -3706,7 +3705,6 @@
|
||||||
"truncated_list_n_more": {
|
"truncated_list_n_more": {
|
||||||
"other": "And %(count)s more..."
|
"other": "And %(count)s more..."
|
||||||
},
|
},
|
||||||
"unknown_device": "Unknown device",
|
|
||||||
"unsupported_browser": {
|
"unsupported_browser": {
|
||||||
"description": "If you continue, some features may stop working and there is a risk that you may lose data in the future. Update your browser to continue using %(brand)s.",
|
"description": "If you continue, some features may stop working and there is a risk that you may lose data in the future. Update your browser to continue using %(brand)s.",
|
||||||
"title": "%(brand)s does not support this browser"
|
"title": "%(brand)s does not support this browser"
|
||||||
|
|
|
@ -27,7 +27,6 @@ import MatrixChat from "../components/structures/MatrixChat";
|
||||||
import { ValidatedServerConfig } from "../utils/ValidatedServerConfig";
|
import { ValidatedServerConfig } from "../utils/ValidatedServerConfig";
|
||||||
import { ModuleRunner } from "../modules/ModuleRunner";
|
import { ModuleRunner } from "../modules/ModuleRunner";
|
||||||
import { parseQs } from "./url_utils";
|
import { parseQs } from "./url_utils";
|
||||||
import VectorBasePlatform from "./platform/VectorBasePlatform";
|
|
||||||
import { getInitialScreenAfterLogin, getScreenFromLocation, init as initRouting, onNewScreen } from "./routing";
|
import { getInitialScreenAfterLogin, getScreenFromLocation, init as initRouting, onNewScreen } from "./routing";
|
||||||
import { UserFriendlyError } from "../languageHandler";
|
import { UserFriendlyError } from "../languageHandler";
|
||||||
|
|
||||||
|
@ -64,7 +63,7 @@ export async function loadApp(fragParams: {}, matrixChatRef: React.Ref<MatrixCha
|
||||||
const urlWithoutQuery = window.location.protocol + "//" + window.location.host + window.location.pathname;
|
const urlWithoutQuery = window.location.protocol + "//" + window.location.host + window.location.pathname;
|
||||||
logger.log("Vector starting at " + urlWithoutQuery);
|
logger.log("Vector starting at " + urlWithoutQuery);
|
||||||
|
|
||||||
(platform as VectorBasePlatform).startUpdater();
|
platform?.startUpdater();
|
||||||
|
|
||||||
// Don't bother loading the app until the config is verified
|
// Don't bother loading the app until the config is verified
|
||||||
const config = await verifyServerConfig();
|
const config = await verifyServerConfig();
|
||||||
|
|
|
@ -15,7 +15,7 @@ import React from "react";
|
||||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { UpdateCheckStatus, UpdateStatus } from "../../BasePlatform";
|
import BasePlatform, { UpdateCheckStatus, UpdateStatus } from "../../BasePlatform";
|
||||||
import BaseEventIndexManager from "../../indexing/BaseEventIndexManager";
|
import BaseEventIndexManager from "../../indexing/BaseEventIndexManager";
|
||||||
import dis from "../../dispatcher/dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import SdkConfig from "../../SdkConfig";
|
import SdkConfig from "../../SdkConfig";
|
||||||
|
@ -35,7 +35,6 @@ import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||||
import { avatarUrlForRoom, getInitialLetter } from "../../Avatar";
|
import { avatarUrlForRoom, getInitialLetter } from "../../Avatar";
|
||||||
import DesktopCapturerSourcePicker from "../../components/views/elements/DesktopCapturerSourcePicker";
|
import DesktopCapturerSourcePicker from "../../components/views/elements/DesktopCapturerSourcePicker";
|
||||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||||
import VectorBasePlatform from "./VectorBasePlatform";
|
|
||||||
import { SeshatIndexManager } from "./SeshatIndexManager";
|
import { SeshatIndexManager } from "./SeshatIndexManager";
|
||||||
import { IPCManager } from "./IPCManager";
|
import { IPCManager } from "./IPCManager";
|
||||||
import { _t } from "../../languageHandler";
|
import { _t } from "../../languageHandler";
|
||||||
|
@ -90,7 +89,7 @@ function getUpdateCheckStatus(status: boolean | string): UpdateStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ElectronPlatform extends VectorBasePlatform {
|
export default class ElectronPlatform extends BasePlatform {
|
||||||
private readonly ipc = new IPCManager("ipcCall", "ipcReply");
|
private readonly ipc = new IPCManager("ipcCall", "ipcReply");
|
||||||
private readonly eventIndexManager: BaseEventIndexManager = new SeshatIndexManager();
|
private readonly eventIndexManager: BaseEventIndexManager = new SeshatIndexManager();
|
||||||
// this is the opaque token we pass to the HS which when we get it in our callback we can resolve to a profile
|
// this is the opaque token we pass to the HS which when we get it in our callback we can resolve to a profile
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2018-2024 New Vector Ltd.
|
|
||||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
|
||||||
Copyright 2016 Aviral Dasgupta
|
|
||||||
Copyright 2016 OpenMarket Ltd
|
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
|
||||||
Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { IConfigOptions } from "../../IConfigOptions";
|
|
||||||
import BasePlatform from "../../BasePlatform";
|
|
||||||
import { getVectorConfig } from "../getconfig";
|
|
||||||
import Favicon from "../../favicon";
|
|
||||||
import { _t } from "../../languageHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vector-specific extensions to the BasePlatform template
|
|
||||||
*/
|
|
||||||
export default abstract class VectorBasePlatform extends BasePlatform {
|
|
||||||
protected _favicon?: Favicon;
|
|
||||||
|
|
||||||
public async getConfig(): Promise<IConfigOptions | undefined> {
|
|
||||||
return getVectorConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
public getHumanReadableName(): string {
|
|
||||||
return "Vector Base Platform"; // no translation required: only used for analytics
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delay creating the `Favicon` instance until first use (on the first notification) as
|
|
||||||
* it uses canvas, which can trigger a permission prompt in Firefox's resist fingerprinting mode.
|
|
||||||
* See https://github.com/element-hq/element-web/issues/9605.
|
|
||||||
*/
|
|
||||||
public get favicon(): Favicon {
|
|
||||||
if (this._favicon) {
|
|
||||||
return this._favicon;
|
|
||||||
}
|
|
||||||
this._favicon = new Favicon();
|
|
||||||
return this._favicon;
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateFavicon(): void {
|
|
||||||
let bgColor = "#d00";
|
|
||||||
let notif: string | number = this.notificationCount;
|
|
||||||
|
|
||||||
if (this.errorDidOccur) {
|
|
||||||
notif = notif || "×";
|
|
||||||
bgColor = "#f00";
|
|
||||||
}
|
|
||||||
|
|
||||||
this.favicon.badge(notif, { bgColor });
|
|
||||||
}
|
|
||||||
|
|
||||||
public setNotificationCount(count: number): void {
|
|
||||||
if (this.notificationCount === count) return;
|
|
||||||
super.setNotificationCount(count);
|
|
||||||
this.updateFavicon();
|
|
||||||
}
|
|
||||||
|
|
||||||
public setErrorStatus(errorDidOccur: boolean): void {
|
|
||||||
if (this.errorDidOccur === errorDidOccur) return;
|
|
||||||
super.setErrorStatus(errorDidOccur);
|
|
||||||
this.updateFavicon();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Begin update polling, if applicable
|
|
||||||
*/
|
|
||||||
public startUpdater(): void {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a sensible default display name for the
|
|
||||||
* device Vector is running on
|
|
||||||
*/
|
|
||||||
public getDefaultDeviceDisplayName(): string {
|
|
||||||
return _t("unknown_device");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,12 +11,11 @@ import UAParser from "ua-parser-js";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||||
import { UpdateCheckStatus, UpdateStatus } from "../../BasePlatform";
|
import BasePlatform, { UpdateCheckStatus, UpdateStatus } from "../../BasePlatform";
|
||||||
import dis from "../../dispatcher/dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import { hideToast as hideUpdateToast, showToast as showUpdateToast } from "../../toasts/UpdateToast";
|
import { hideToast as hideUpdateToast, showToast as showUpdateToast } from "../../toasts/UpdateToast";
|
||||||
import { Action } from "../../dispatcher/actions";
|
import { Action } from "../../dispatcher/actions";
|
||||||
import { CheckUpdatesPayload } from "../../dispatcher/payloads/CheckUpdatesPayload";
|
import { CheckUpdatesPayload } from "../../dispatcher/payloads/CheckUpdatesPayload";
|
||||||
import VectorBasePlatform from "./VectorBasePlatform";
|
|
||||||
import { parseQs } from "../url_utils";
|
import { parseQs } from "../url_utils";
|
||||||
import { _t } from "../../languageHandler";
|
import { _t } from "../../languageHandler";
|
||||||
|
|
||||||
|
@ -31,7 +30,7 @@ function getNormalizedAppVersion(version: string): string {
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class WebPlatform extends VectorBasePlatform {
|
export default class WebPlatform extends BasePlatform {
|
||||||
private static readonly VERSION = process.env.VERSION!; // baked in by Webpack
|
private static readonly VERSION = process.env.VERSION!; // baked in by Webpack
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
|
|
|
@ -953,7 +953,7 @@ describe("<MatrixChat />", () => {
|
||||||
const getComponentAndWaitForReady = async (): Promise<RenderResult> => {
|
const getComponentAndWaitForReady = async (): Promise<RenderResult> => {
|
||||||
const renderResult = getComponent();
|
const renderResult = getComponent();
|
||||||
// wait for welcome page chrome render
|
// wait for welcome page chrome render
|
||||||
await screen.findByText("powered by Matrix");
|
await screen.findByText("Powered by Matrix");
|
||||||
|
|
||||||
// go to login page
|
// go to login page
|
||||||
defaultDispatcher.dispatch({
|
defaultDispatcher.dispatch({
|
||||||
|
@ -1480,7 +1480,7 @@ describe("<MatrixChat />", () => {
|
||||||
const getComponentAndWaitForReady = async (): Promise<RenderResult> => {
|
const getComponentAndWaitForReady = async (): Promise<RenderResult> => {
|
||||||
const renderResult = getComponent();
|
const renderResult = getComponent();
|
||||||
// wait for welcome page chrome render
|
// wait for welcome page chrome render
|
||||||
await screen.findByText("powered by Matrix");
|
await screen.findByText("Powered by Matrix");
|
||||||
|
|
||||||
// go to mobile_register page
|
// go to mobile_register page
|
||||||
defaultDispatcher.dispatch({
|
defaultDispatcher.dispatch({
|
||||||
|
@ -1500,7 +1500,7 @@ describe("<MatrixChat />", () => {
|
||||||
it("should render welcome screen if mobile registration is not enabled in settings", async () => {
|
it("should render welcome screen if mobile registration is not enabled in settings", async () => {
|
||||||
await getComponentAndWaitForReady();
|
await getComponentAndWaitForReady();
|
||||||
|
|
||||||
await screen.findByText("powered by Matrix");
|
await screen.findByText("Powered by Matrix");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render mobile registration", async () => {
|
it("should render mobile registration", async () => {
|
||||||
|
|
|
@ -114,6 +114,15 @@ exports[`<MatrixChat /> Multi-tab lockout waits for other tab to stop during sta
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_AuthPage_modal"
|
class="mx_AuthPage_modal"
|
||||||
|
style="position: relative;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_AuthPage_modalBlur"
|
||||||
|
style="position: absolute; top: 0px; right: 0px; bottom: 0px; left: 0px; filter: blur(40px);"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="mx_AuthPage_modalContent"
|
||||||
|
style="display: flex; z-index: 1; background: rgba(255, 255, 255, 0.59); border-radius: 8px;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_Welcome"
|
class="mx_Welcome"
|
||||||
|
@ -158,16 +167,38 @@ exports[`<MatrixChat /> Multi-tab lockout waits for other tab to stop during sta
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<footer
|
<footer
|
||||||
class="mx_AuthFooter"
|
class="mx_AuthFooter"
|
||||||
role="contentinfo"
|
role="contentinfo"
|
||||||
>
|
>
|
||||||
|
<a
|
||||||
|
href="https://element.io/blog"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Blog
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://twitter.com/element_hq"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Twitter
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://github.com/element-hq/element-web"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
<a
|
<a
|
||||||
href="https://matrix.org"
|
href="https://matrix.org"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
powered by Matrix
|
Powered by Matrix
|
||||||
</a>
|
</a>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
@ -201,6 +232,15 @@ exports[`<MatrixChat /> with a soft-logged-out session should show the soft-logo
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_AuthPage_modal"
|
class="mx_AuthPage_modal"
|
||||||
|
style="position: relative;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_AuthPage_modalBlur"
|
||||||
|
style="position: absolute; top: 0px; right: 0px; bottom: 0px; left: 0px; filter: blur(40px);"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="mx_AuthPage_modalContent"
|
||||||
|
style="display: flex; z-index: 1; background: rgba(255, 255, 255, 0.59); border-radius: 8px;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_AuthHeader"
|
class="mx_AuthHeader"
|
||||||
|
@ -208,7 +248,10 @@ exports[`<MatrixChat /> with a soft-logged-out session should show the soft-logo
|
||||||
<aside
|
<aside
|
||||||
class="mx_AuthHeaderLogo"
|
class="mx_AuthHeaderLogo"
|
||||||
>
|
>
|
||||||
Matrix
|
<img
|
||||||
|
alt="Element"
|
||||||
|
src="themes/element/img/logos/element-logo.svg"
|
||||||
|
/>
|
||||||
</aside>
|
</aside>
|
||||||
<div
|
<div
|
||||||
class="mx_Dropdown mx_LanguageDropdown mx_AuthBody_language"
|
class="mx_Dropdown mx_LanguageDropdown mx_AuthBody_language"
|
||||||
|
@ -301,16 +344,38 @@ exports[`<MatrixChat /> with a soft-logged-out session should show the soft-logo
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<footer
|
<footer
|
||||||
class="mx_AuthFooter"
|
class="mx_AuthFooter"
|
||||||
role="contentinfo"
|
role="contentinfo"
|
||||||
>
|
>
|
||||||
|
<a
|
||||||
|
href="https://element.io/blog"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Blog
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://twitter.com/element_hq"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Twitter
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://github.com/element-hq/element-web"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
<a
|
<a
|
||||||
href="https://matrix.org"
|
href="https://matrix.org"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
powered by Matrix
|
Powered by Matrix
|
||||||
</a>
|
</a>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,16 +9,16 @@ Please see LICENSE files in the repository root for full details.
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { render } from "jest-matrix-react";
|
import { render } from "jest-matrix-react";
|
||||||
|
|
||||||
import VectorAuthPage from "../../../../../src/components/views/auth/VectorAuthPage";
|
import AuthFooter from "../../../../../src/components/views/auth/AuthFooter";
|
||||||
import { setupLanguageMock } from "../../../../setup/setupLanguage";
|
import { setupLanguageMock } from "../../../../setup/setupLanguage";
|
||||||
|
|
||||||
describe("<VectorAuthPage />", () => {
|
describe("<AuthFooter />", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setupLanguageMock();
|
setupLanguageMock();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should match snapshot", () => {
|
it("should match snapshot", () => {
|
||||||
const { asFragment } = render(<VectorAuthPage />);
|
const { asFragment } = render(<AuthFooter />);
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -9,11 +9,11 @@ Please see LICENSE files in the repository root for full details.
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { render } from "jest-matrix-react";
|
import { render } from "jest-matrix-react";
|
||||||
|
|
||||||
import VectorAuthHeaderLogo from "../../../../../src/components/views/auth/VectorAuthHeaderLogo";
|
import AuthHeaderLogo from "../../../../../src/components/views/auth/AuthHeaderLogo";
|
||||||
|
|
||||||
describe("<VectorAuthHeaderLogo />", () => {
|
describe("<AuthHeaderLogo />", () => {
|
||||||
it("should match snapshot", () => {
|
it("should match snapshot", () => {
|
||||||
const { asFragment } = render(<VectorAuthHeaderLogo />);
|
const { asFragment } = render(<AuthHeaderLogo />);
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024 New Vector Ltd.
|
||||||
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import { render } from "jest-matrix-react";
|
||||||
|
|
||||||
|
import AuthPage from "../../../../../src/components/views/auth/AuthPage";
|
||||||
|
import { setupLanguageMock } from "../../../../setup/setupLanguage";
|
||||||
|
import SdkConfig from "../../../../../src/SdkConfig.ts";
|
||||||
|
|
||||||
|
describe("<AuthPage />", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
setupLanguageMock();
|
||||||
|
SdkConfig.reset();
|
||||||
|
// @ts-ignore private access
|
||||||
|
AuthPage.welcomeBackgroundUrl = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should match snapshot", () => {
|
||||||
|
const { asFragment } = render(<AuthPage />);
|
||||||
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use configured background url", () => {
|
||||||
|
SdkConfig.add({ branding: { welcome_background_url: ["https://example.com/image.png"] } });
|
||||||
|
const { container } = render(<AuthPage />);
|
||||||
|
expect(container.querySelector(".mx_AuthPage")).toHaveStyle({
|
||||||
|
background: "center/cover fixed url(https://example.com/image.png)",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,24 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2024 New Vector Ltd.
|
|
||||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
|
||||||
Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import { render } from "jest-matrix-react";
|
|
||||||
|
|
||||||
import VectorAuthFooter from "../../../../../src/components/views/auth/VectorAuthFooter";
|
|
||||||
import { setupLanguageMock } from "../../../../setup/setupLanguage";
|
|
||||||
|
|
||||||
describe("<VectorAuthFooter />", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
setupLanguageMock();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should match snapshot", () => {
|
|
||||||
const { asFragment } = render(<VectorAuthFooter />);
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`<VectorAuthFooter /> should match snapshot 1`] = `
|
exports[`<AuthFooter /> should match snapshot 1`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<footer
|
<footer
|
||||||
class="mx_AuthFooter"
|
class="mx_AuthFooter"
|
|
@ -1,6 +1,6 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`<VectorAuthHeaderLogo /> should match snapshot 1`] = `
|
exports[`<AuthHeaderLogo /> should match snapshot 1`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<aside
|
<aside
|
||||||
class="mx_AuthHeaderLogo"
|
class="mx_AuthHeaderLogo"
|
|
@ -1,6 +1,6 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`<VectorAuthPage /> should match snapshot 1`] = `
|
exports[`<AuthPage /> should match snapshot 1`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<div
|
<div
|
||||||
class="mx_AuthPage"
|
class="mx_AuthPage"
|
|
@ -229,4 +229,18 @@ describe("WebPlatform", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should return config from config.json", async () => {
|
||||||
|
window.location.hostname = "domain.com";
|
||||||
|
fetchMock.get(/config\.json.*/, { brand: "test" });
|
||||||
|
const platform = new WebPlatform();
|
||||||
|
await expect(platform.getConfig()).resolves.toEqual(expect.objectContaining({ brand: "test" }));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should re-render favicon when setting error status", () => {
|
||||||
|
const platform = new WebPlatform();
|
||||||
|
const spy = jest.spyOn(platform.favicon, "badge");
|
||||||
|
platform.setErrorStatus(true);
|
||||||
|
expect(spy).toHaveBeenCalledWith(expect.anything(), { bgColor: "#f00" });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue