Merge branch 'livekit' into toger5/track-processor-blur
This commit is contained in:
10
src/settings/DeveloperSettingsTab.module.css
Normal file
10
src/settings/DeveloperSettingsTab.module.css
Normal file
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-size: var(--font-size-micro);
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
Copyright 2022-2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type ChangeEvent, type FC, useCallback } from "react";
|
||||
import { type ChangeEvent, type FC, useCallback, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { FieldRow, InputField } from "../input/Input";
|
||||
@@ -15,14 +15,18 @@ import {
|
||||
debugTileLayout as debugTileLayoutSetting,
|
||||
showNonMemberTiles as showNonMemberTilesSetting,
|
||||
showConnectionStats as showConnectionStatsSetting,
|
||||
useNewMembershipManagerSetting,
|
||||
} from "./settings";
|
||||
import type { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
|
||||
import type { MatrixClient } from "matrix-js-sdk";
|
||||
import type { Room as LivekitRoom } from "livekit-client";
|
||||
import styles from "./DeveloperSettingsTab.module.css";
|
||||
import { useUrlParams } from "../UrlParams";
|
||||
interface Props {
|
||||
client: MatrixClient;
|
||||
livekitRoom?: LivekitRoom;
|
||||
}
|
||||
|
||||
export const DeveloperSettingsTab: FC<Props> = ({ client }) => {
|
||||
export const DeveloperSettingsTab: FC<Props> = ({ client, livekitRoom }) => {
|
||||
const { t } = useTranslation();
|
||||
const [duplicateTiles, setDuplicateTiles] = useSetting(duplicateTilesSetting);
|
||||
const [debugTileLayout, setDebugTileLayout] = useSetting(
|
||||
@@ -36,6 +40,22 @@ export const DeveloperSettingsTab: FC<Props> = ({ client }) => {
|
||||
showConnectionStatsSetting,
|
||||
);
|
||||
|
||||
const [useNewMembershipManager, setNewMembershipManager] = useSetting(
|
||||
useNewMembershipManagerSetting,
|
||||
);
|
||||
|
||||
const urlParams = useUrlParams();
|
||||
|
||||
const sfuUrl = useMemo((): URL | null => {
|
||||
if (livekitRoom?.engine.client.ws?.url) {
|
||||
// strip the URL params
|
||||
const url = new URL(livekitRoom.engine.client.ws.url);
|
||||
url.search = "";
|
||||
return url;
|
||||
}
|
||||
return null;
|
||||
}, [livekitRoom]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
@@ -122,6 +142,40 @@ export const DeveloperSettingsTab: FC<Props> = ({ client }) => {
|
||||
)}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="useNewMembershipManager"
|
||||
type="checkbox"
|
||||
label={t("developer_mode.use_new_membership_manager")}
|
||||
checked={!!useNewMembershipManager}
|
||||
onChange={useCallback(
|
||||
(event: ChangeEvent<HTMLInputElement>): void => {
|
||||
setNewMembershipManager(event.target.checked);
|
||||
},
|
||||
[setNewMembershipManager],
|
||||
)}
|
||||
/>
|
||||
</FieldRow>
|
||||
{livekitRoom ? (
|
||||
<>
|
||||
<p>
|
||||
{t("developer_mode.livekit_sfu", {
|
||||
url: sfuUrl?.href || "unknown",
|
||||
})}
|
||||
</p>
|
||||
<p>{t("developer_mode.livekit_server_info")}</p>
|
||||
<pre className={styles.pre}>
|
||||
{livekitRoom.serverInfo
|
||||
? JSON.stringify(livekitRoom.serverInfo, null, 2)
|
||||
: "undefined"}
|
||||
{livekitRoom.metadata}
|
||||
</pre>
|
||||
</>
|
||||
) : null}
|
||||
<p>{t("developer_mode.environment_variables")}</p>
|
||||
<pre>{JSON.stringify(import.meta.env, null, 2)}</pre>
|
||||
<p>{t("developer_mode.url_params")}</p>
|
||||
<pre>{JSON.stringify(urlParams, null, 2)}</pre>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
/*
|
||||
Copyright 2022-2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type ChangeEvent, type FC, useCallback } from "react";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { secureRandomString } from "matrix-js-sdk/lib/randomstring";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { Button, Text } from "@vector-im/compound-web";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { logger } from "matrix-js-sdk/lib/logger";
|
||||
|
||||
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
|
||||
import { useSubmitRageshake, useRageshakeRequest } from "./submit-rageshake";
|
||||
@@ -23,7 +23,8 @@ interface Props {
|
||||
|
||||
export const FeedbackSettingsTab: FC<Props> = ({ roomId }) => {
|
||||
const { t } = useTranslation();
|
||||
const { submitRageshake, sending, sent, error } = useSubmitRageshake();
|
||||
const { submitRageshake, sending, sent, error, available } =
|
||||
useSubmitRageshake();
|
||||
const sendRageshakeRequest = useRageshakeRequest();
|
||||
|
||||
const onSubmitFeedback = useCallback(
|
||||
@@ -36,7 +37,7 @@ export const FeedbackSettingsTab: FC<Props> = ({ roomId }) => {
|
||||
const description =
|
||||
typeof descriptionData === "string" ? descriptionData : "";
|
||||
const sendLogs = Boolean(data.get("sendLogs"));
|
||||
const rageshakeRequestId = randomString(16);
|
||||
const rageshakeRequestId = secureRandomString(16);
|
||||
|
||||
submitRageshake({
|
||||
description,
|
||||
@@ -66,20 +67,27 @@ export const FeedbackSettingsTab: FC<Props> = ({ roomId }) => {
|
||||
</Text>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4>{t("common.analytics")}</h4>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="optInAnalytics"
|
||||
type="checkbox"
|
||||
checked={optInAnalytics ?? undefined}
|
||||
description={optInDescription}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>): void => {
|
||||
setOptInAnalytics?.(event.target.checked);
|
||||
}}
|
||||
/>
|
||||
</FieldRow>
|
||||
// in the embedded package the widget host is responsible for analytics consent
|
||||
const analyticsConsentBlock =
|
||||
import.meta.env.VITE_PACKAGE === "embedded" ? null : (
|
||||
<>
|
||||
<h4>{t("common.analytics")}</h4>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="optInAnalytics"
|
||||
type="checkbox"
|
||||
checked={optInAnalytics ?? undefined}
|
||||
description={optInDescription}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>): void => {
|
||||
setOptInAnalytics?.(event.target.checked);
|
||||
}}
|
||||
/>
|
||||
</FieldRow>
|
||||
</>
|
||||
);
|
||||
|
||||
const feedbackBlock = available ? (
|
||||
<>
|
||||
<h4>{t("settings.feedback_tab_h4")}</h4>
|
||||
<Text>{t("settings.feedback_tab_body")}</Text>
|
||||
<form onSubmit={onSubmitFeedback}>
|
||||
@@ -113,6 +121,13 @@ export const FeedbackSettingsTab: FC<Props> = ({ roomId }) => {
|
||||
{sent && <Text>{t("settings.feedback_tab_thank_you")}</Text>}
|
||||
</FieldRow>
|
||||
</form>
|
||||
</>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{analyticsConsentBlock}
|
||||
{feedbackBlock}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
Copyright 2022-2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/*
|
||||
Copyright 2022-2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type FC, useCallback, useEffect, useMemo, useRef } from "react";
|
||||
import { type MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { type MatrixClient } from "matrix-js-sdk";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { logger } from "matrix-js-sdk/lib/logger";
|
||||
|
||||
import { useProfile } from "../profile/useProfile";
|
||||
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
Copyright 2022-2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/*
|
||||
Copyright 2023, 2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { type FC, useCallback } from "react";
|
||||
import { type FC, useCallback, type JSX } from "react";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { logger } from "matrix-js-sdk/lib/logger";
|
||||
|
||||
import { Config } from "../config/Config";
|
||||
import styles from "./RageshakeButton.module.css";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
Copyright 2022-2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
/*
|
||||
Copyright 2022-2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type FC, type ReactNode, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { Root as Form, Separator } from "@vector-im/compound-web";
|
||||
import { type MatrixClient } from "matrix-js-sdk";
|
||||
import { Root as Form ,Separator} from "@vector-im/compound-web";
|
||||
import { type Room as LivekitRoom } from "livekit-client";
|
||||
|
||||
import { Modal } from "../Modal";
|
||||
import styles from "./SettingsModal.module.css";
|
||||
@@ -32,6 +33,7 @@ import { DeviceSelection } from "./DeviceSelection";
|
||||
import { useTrackProcessor } from "../livekit/TrackProcessorContext";
|
||||
import { DeveloperSettingsTab } from "./DeveloperSettingsTab";
|
||||
import { FieldRow, InputField } from "../input/Input";
|
||||
import { useSubmitRageshake } from "./submit-rageshake";
|
||||
|
||||
type SettingsTab =
|
||||
| "audio"
|
||||
@@ -49,6 +51,7 @@ interface Props {
|
||||
onTabChange: (tab: SettingsTab) => void;
|
||||
client: MatrixClient;
|
||||
roomId?: string;
|
||||
livekitRoom?: LivekitRoom;
|
||||
}
|
||||
|
||||
export const defaultSettingsTab: SettingsTab = "audio";
|
||||
@@ -60,6 +63,7 @@ export const SettingsModal: FC<Props> = ({
|
||||
onTabChange,
|
||||
client,
|
||||
roomId,
|
||||
livekitRoom,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -98,6 +102,8 @@ export const SettingsModal: FC<Props> = ({
|
||||
|
||||
const [showDeveloperSettingsTab] = useSetting(developerMode);
|
||||
|
||||
const { available: isRageshakeAvailable } = useSubmitRageshake();
|
||||
|
||||
const audioTab: Tab<SettingsTab> = {
|
||||
key: "audio",
|
||||
name: t("common.audio"),
|
||||
@@ -173,12 +179,17 @@ export const SettingsModal: FC<Props> = ({
|
||||
const developerTab: Tab<SettingsTab> = {
|
||||
key: "developer",
|
||||
name: t("settings.developer_tab_title"),
|
||||
content: <DeveloperSettingsTab client={client} />,
|
||||
content: <DeveloperSettingsTab client={client} livekitRoom={livekitRoom} />,
|
||||
};
|
||||
|
||||
const tabs = [audioTab, videoTab];
|
||||
if (widget === null) tabs.push(profileTab);
|
||||
tabs.push(preferencesTab, feedbackTab);
|
||||
tabs.push(preferencesTab);
|
||||
if (isRageshakeAvailable || import.meta.env.VITE_PACKAGE === "full") {
|
||||
// for full package we want to show the analytics consent checkbox
|
||||
// even if rageshake is not available
|
||||
tabs.push(feedbackTab);
|
||||
}
|
||||
if (showDeveloperSettingsTab) tabs.push(developerTab);
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Copyright 2018-2024 New Vector Ltd.
|
||||
Copyright 2017 OpenMarket Ltd
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
@@ -29,8 +29,8 @@ Please see LICENSE in the repository root for full details.
|
||||
|
||||
import EventEmitter from "events";
|
||||
import { throttle } from "lodash-es";
|
||||
import { type Logger, logger } from "matrix-js-sdk/src/logger";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { type Logger, logger } from "matrix-js-sdk/lib/logger";
|
||||
import { secureRandomString } from "matrix-js-sdk/lib/randomstring";
|
||||
import { type LoggingMethod } from "loglevel";
|
||||
|
||||
import type loglevel from "loglevel";
|
||||
@@ -128,7 +128,7 @@ class IndexedDBLogStore {
|
||||
private indexedDB: IDBFactory,
|
||||
private loggerInstance: ConsoleLogger,
|
||||
) {
|
||||
this.id = "instance-" + randomString(16);
|
||||
this.id = "instance-" + secureRandomString(16);
|
||||
|
||||
loggerInstance.on(ConsoleLoggerEvent.Log, this.onLoggerLog);
|
||||
window.addEventListener("beforeunload", () => {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { logger } from "matrix-js-sdk/lib/logger";
|
||||
import { BehaviorSubject, type Observable } from "rxjs";
|
||||
import { useObservableEagerState } from "observable-hooks";
|
||||
|
||||
@@ -115,4 +115,8 @@ export const soundEffectVolumeSetting = new Setting<number>(
|
||||
0.5,
|
||||
);
|
||||
|
||||
export const useNewMembershipManagerSetting = new Setting<boolean>(
|
||||
"new-membership-manager",
|
||||
true,
|
||||
);
|
||||
export const alwaysShowSelf = new Setting<boolean>("always-show-self", true);
|
||||
|
||||
87
src/settings/submit-rageshake.test.ts
Normal file
87
src/settings/submit-rageshake.test.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import {
|
||||
expect,
|
||||
describe,
|
||||
it,
|
||||
afterEach,
|
||||
vi,
|
||||
type Mock,
|
||||
beforeEach,
|
||||
} from "vitest";
|
||||
|
||||
import { getRageshakeSubmitUrl } from "./submit-rageshake";
|
||||
import { getUrlParams } from "../UrlParams";
|
||||
import { mockConfig } from "../utils/test";
|
||||
|
||||
vi.mock("../UrlParams", () => ({ getUrlParams: vi.fn() }));
|
||||
|
||||
describe("getRageshakeSubmitUrl", () => {
|
||||
beforeEach(() => {
|
||||
(getUrlParams as Mock).mockReturnValue({});
|
||||
mockConfig({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("embedded package", () => {
|
||||
beforeEach(() => {
|
||||
vi.stubEnv("VITE_PACKAGE", "embedded");
|
||||
});
|
||||
|
||||
it("returns undefined no rageshakeSubmitUrl URL param", () => {
|
||||
expect(getRageshakeSubmitUrl()).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns rageshakeSubmitUrl URL param when set", () => {
|
||||
(getUrlParams as Mock).mockReturnValue({
|
||||
rageshakeSubmitUrl: "https://url.example.com.localhost",
|
||||
});
|
||||
expect(getRageshakeSubmitUrl()).toBe("https://url.example.com.localhost");
|
||||
});
|
||||
|
||||
it("ignores config param and returns undefined", () => {
|
||||
mockConfig({
|
||||
rageshake: {
|
||||
submit_url: "https://config.example.com.localhost",
|
||||
},
|
||||
});
|
||||
expect(getRageshakeSubmitUrl()).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("full package", () => {
|
||||
beforeEach(() => {
|
||||
vi.stubEnv("VITE_PACKAGE", "full");
|
||||
});
|
||||
it("returns undefined with no config value", () => {
|
||||
expect(getRageshakeSubmitUrl()).toBeUndefined();
|
||||
});
|
||||
|
||||
it("ignores rageshakeSubmitUrl URL param and returns undefined", () => {
|
||||
(getUrlParams as Mock).mockReturnValue({
|
||||
rageshakeSubmitUrl: "https://url.example.com.localhost",
|
||||
});
|
||||
expect(getRageshakeSubmitUrl()).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns config value when set", () => {
|
||||
mockConfig({
|
||||
rageshake: {
|
||||
submit_url: "https://config.example.com.localhost",
|
||||
},
|
||||
});
|
||||
expect(getRageshakeSubmitUrl()).toBe(
|
||||
"https://config.example.com.localhost",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,24 +1,25 @@
|
||||
/*
|
||||
Copyright 2022-2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type ComponentProps, useCallback, useEffect, useState } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { logger } from "matrix-js-sdk/lib/logger";
|
||||
import {
|
||||
ClientEvent,
|
||||
type Crypto,
|
||||
type MatrixClient,
|
||||
type MatrixEvent,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
} from "matrix-js-sdk";
|
||||
import { type CryptoApi } from "matrix-js-sdk/lib/crypto-api";
|
||||
|
||||
import { getLogsForReport } from "./rageshake";
|
||||
import { useClient } from "../ClientContext";
|
||||
import { Config } from "../config/Config";
|
||||
import { ElementCallOpenTelemetry } from "../otel/otel";
|
||||
import { type RageshakeRequestModal } from "../room/RageshakeRequestModal";
|
||||
import { getUrlParams } from "../UrlParams";
|
||||
|
||||
const gzip = async (text: string): Promise<Blob> => {
|
||||
// pako is relatively large (200KB), so we only import it when needed
|
||||
@@ -34,7 +35,7 @@ const gzip = async (text: string): Promise<Blob> => {
|
||||
* Collects crypto related information.
|
||||
*/
|
||||
async function collectCryptoInfo(
|
||||
cryptoApi: Crypto.CryptoApi,
|
||||
cryptoApi: CryptoApi,
|
||||
body: FormData,
|
||||
): Promise<void> {
|
||||
body.append("crypto_version", cryptoApi.getVersion());
|
||||
@@ -82,7 +83,7 @@ async function collectCryptoInfo(
|
||||
*/
|
||||
async function collectRecoveryInfo(
|
||||
client: MatrixClient,
|
||||
cryptoApi: Crypto.CryptoApi,
|
||||
cryptoApi: CryptoApi,
|
||||
body: FormData,
|
||||
): Promise<void> {
|
||||
const secretStorage = client.secretStorage;
|
||||
@@ -116,11 +117,28 @@ interface RageShakeSubmitOptions {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export function useSubmitRageshake(): {
|
||||
export function getRageshakeSubmitUrl(): string | undefined {
|
||||
if (import.meta.env.VITE_PACKAGE === "full") {
|
||||
// in full package we always use the one configured on the server
|
||||
return Config.get().rageshake?.submit_url;
|
||||
}
|
||||
|
||||
if (import.meta.env.VITE_PACKAGE === "embedded") {
|
||||
// in embedded package we always use the one provided by the widget host
|
||||
return getUrlParams().rageshakeSubmitUrl ?? undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function useSubmitRageshake(
|
||||
injectedGetRageshakeSubmitUrl = getRageshakeSubmitUrl,
|
||||
): {
|
||||
submitRageshake: (opts: RageShakeSubmitOptions) => Promise<void>;
|
||||
sending: boolean;
|
||||
sent: boolean;
|
||||
error?: Error;
|
||||
available: boolean;
|
||||
} {
|
||||
const { client } = useClient();
|
||||
|
||||
@@ -138,7 +156,8 @@ export function useSubmitRageshake(): {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
async (opts) => {
|
||||
if (!Config.get().rageshake?.submit_url) {
|
||||
const submitUrl = injectedGetRageshakeSubmitUrl();
|
||||
if (!submitUrl) {
|
||||
throw new Error("No rageshake URL is configured");
|
||||
}
|
||||
|
||||
@@ -272,7 +291,7 @@ export function useSubmitRageshake(): {
|
||||
);
|
||||
}
|
||||
|
||||
const res = await fetch(Config.get().rageshake!.submit_url, {
|
||||
const res = await fetch(submitUrl, {
|
||||
method: "POST",
|
||||
body,
|
||||
});
|
||||
@@ -289,7 +308,7 @@ export function useSubmitRageshake(): {
|
||||
logger.error(error);
|
||||
}
|
||||
},
|
||||
[client, sending],
|
||||
[client, sending, injectedGetRageshakeSubmitUrl],
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -297,6 +316,7 @@ export function useSubmitRageshake(): {
|
||||
sending,
|
||||
sent,
|
||||
error,
|
||||
available: !!injectedGetRageshakeSubmitUrl(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
222
src/settings/useSubmitRageshake.test.tsx
Normal file
222
src/settings/useSubmitRageshake.test.tsx
Normal file
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import {
|
||||
expect,
|
||||
describe,
|
||||
it,
|
||||
vi,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
type Mock,
|
||||
} from "vitest";
|
||||
import { useState, type ReactElement } from "react";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import { type MatrixClient } from "matrix-js-sdk/lib/client";
|
||||
|
||||
import { useSubmitRageshake, getRageshakeSubmitUrl } from "./submit-rageshake";
|
||||
import { ClientContextProvider } from "../ClientContext";
|
||||
import { getUrlParams } from "../UrlParams";
|
||||
import { mockConfig } from "../utils/test";
|
||||
|
||||
vi.mock("../UrlParams", () => ({ getUrlParams: vi.fn() }));
|
||||
|
||||
const TestComponent = ({
|
||||
sendLogs,
|
||||
getRageshakeSubmitUrl,
|
||||
}: {
|
||||
sendLogs: boolean;
|
||||
getRageshakeSubmitUrl: () => string | undefined;
|
||||
}): ReactElement => {
|
||||
const [clickError, setClickError] = useState<Error | null>(null);
|
||||
const { available, sending, sent, submitRageshake, error } =
|
||||
useSubmitRageshake(getRageshakeSubmitUrl);
|
||||
|
||||
const onClick = (): void => {
|
||||
submitRageshake({
|
||||
sendLogs,
|
||||
}).catch((e) => {
|
||||
setClickError(e);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p data-testid="available">{available ? "true" : "false"}</p>
|
||||
<p data-testid="sending">{sending ? "true" : "false"}</p>
|
||||
<p data-testid="sent">{sent ? "true" : "false"}</p>
|
||||
<p data-testid="error">{error?.message}</p>
|
||||
<p data-testid="clickError">{clickError?.message}</p>
|
||||
<button onClick={onClick} data-testid="submit">
|
||||
submit
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function renderWithMockClient(
|
||||
getRageshakeSubmitUrl: () => string | undefined,
|
||||
sendLogs: boolean,
|
||||
): void {
|
||||
const client = vi.mocked<MatrixClient>({
|
||||
getUserId: vi.fn().mockReturnValue("@user:localhost"),
|
||||
getUser: vi.fn().mockReturnValue(null),
|
||||
credentials: {
|
||||
userId: "@user:localhost",
|
||||
},
|
||||
getCrypto: vi.fn().mockReturnValue(undefined),
|
||||
} as unknown as MatrixClient);
|
||||
|
||||
render(
|
||||
<ClientContextProvider
|
||||
value={{
|
||||
state: "valid",
|
||||
disconnected: false,
|
||||
supportedFeatures: {
|
||||
reactions: true,
|
||||
thumbnails: true,
|
||||
},
|
||||
setClient: vi.fn(),
|
||||
authenticated: {
|
||||
client,
|
||||
isPasswordlessUser: true,
|
||||
changePassword: vi.fn(),
|
||||
logout: vi.fn(),
|
||||
},
|
||||
}}
|
||||
>
|
||||
<TestComponent
|
||||
sendLogs={sendLogs}
|
||||
getRageshakeSubmitUrl={getRageshakeSubmitUrl}
|
||||
/>
|
||||
</ClientContextProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
describe("useSubmitRageshake", () => {
|
||||
describe("available", () => {
|
||||
beforeEach(() => {
|
||||
(getUrlParams as Mock).mockReturnValue({});
|
||||
mockConfig({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("embedded package", () => {
|
||||
beforeEach(() => {
|
||||
vi.stubEnv("VITE_PACKAGE", "embedded");
|
||||
});
|
||||
|
||||
it("returns false with no rageshakeSubmitUrl URL param", () => {
|
||||
renderWithMockClient(getRageshakeSubmitUrl, false);
|
||||
expect(screen.getByTestId("available").textContent).toBe("false");
|
||||
});
|
||||
|
||||
it("ignores config value and returns false with no rageshakeSubmitUrl URL param", () => {
|
||||
mockConfig({
|
||||
rageshake: {
|
||||
submit_url: "https://config.example.com.localhost",
|
||||
},
|
||||
});
|
||||
renderWithMockClient(getRageshakeSubmitUrl, false);
|
||||
expect(screen.getByTestId("available").textContent).toBe("false");
|
||||
});
|
||||
|
||||
it("returns true with rageshakeSubmitUrl URL param", () => {
|
||||
(getUrlParams as Mock).mockReturnValue({
|
||||
rageshakeSubmitUrl: "https://url.example.com.localhost",
|
||||
});
|
||||
renderWithMockClient(getRageshakeSubmitUrl, false);
|
||||
expect(screen.getByTestId("available").textContent).toBe("true");
|
||||
});
|
||||
});
|
||||
|
||||
describe("full package", () => {
|
||||
beforeEach(() => {
|
||||
mockConfig({});
|
||||
vi.stubEnv("VITE_PACKAGE", "full");
|
||||
});
|
||||
it("returns false with no config value", () => {
|
||||
renderWithMockClient(getRageshakeSubmitUrl, false);
|
||||
expect(screen.getByTestId("available").textContent).toBe("false");
|
||||
});
|
||||
|
||||
it("ignores rageshakeSubmitUrl URL param and returns false with no config value", () => {
|
||||
(getUrlParams as Mock).mockReturnValue({
|
||||
rageshakeSubmitUrl: "https://url.example.com.localhost",
|
||||
});
|
||||
renderWithMockClient(getRageshakeSubmitUrl, false);
|
||||
expect(screen.getByTestId("available").textContent).toBe("false");
|
||||
});
|
||||
|
||||
it("returns true with config value", () => {
|
||||
mockConfig({
|
||||
rageshake: {
|
||||
submit_url: "https://config.example.com.localhost",
|
||||
},
|
||||
});
|
||||
renderWithMockClient(getRageshakeSubmitUrl, false);
|
||||
expect(screen.getByTestId("available").textContent).toBe("true");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when rageshake is available", () => {
|
||||
beforeEach(() => {
|
||||
mockConfig({});
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("starts unsent", () => {
|
||||
renderWithMockClient(() => "https://rageshake.localhost/foo", false);
|
||||
expect(screen.getByTestId("sending").textContent).toBe("false");
|
||||
expect(screen.getByTestId("sent").textContent).toBe("false");
|
||||
});
|
||||
|
||||
it("submitRageshake fetches expected URL", async () => {
|
||||
const fetchFn = vi.fn().mockResolvedValue({
|
||||
status: 200,
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchFn);
|
||||
|
||||
renderWithMockClient(() => "https://rageshake.localhost/foo", false);
|
||||
screen.getByTestId("submit").click();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("sent").textContent).toBe("true");
|
||||
});
|
||||
expect(fetchFn).toHaveBeenCalledExactlyOnceWith(
|
||||
"https://rageshake.localhost/foo",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
}),
|
||||
);
|
||||
expect(screen.getByTestId("clickError").textContent).toBe("");
|
||||
expect(screen.getByTestId("error").textContent).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when rageshake is not available", () => {
|
||||
it("starts unsent", () => {
|
||||
renderWithMockClient(() => undefined, false);
|
||||
expect(screen.getByTestId("sending").textContent).toBe("false");
|
||||
expect(screen.getByTestId("sent").textContent).toBe("false");
|
||||
});
|
||||
|
||||
it("submitRageshake throws error", async () => {
|
||||
renderWithMockClient(() => undefined, false);
|
||||
screen.getByTestId("submit").click();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("clickError").textContent).toBe(
|
||||
"No rageshake URL is configured",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user