Fix share button in discovery settings being disabled incorrectly (#29151)
* Fix share button in discovery settings being disabled incorrectly Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Improve types & add tests 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> * Add missing snapshot Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
+12
-21
@@ -7,11 +7,12 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import classNames from "classnames";
|
||||
import { SERVICE_TYPES, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { SERVICE_TYPES, MatrixClient, Terms, Policy, InternationalisedPolicy } from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import Modal from "./Modal";
|
||||
import TermsDialog from "./components/views/dialogs/TermsDialog";
|
||||
import { pickBestLanguage } from "./languageHandler.tsx";
|
||||
|
||||
export class TermsNotSignedError extends Error {}
|
||||
|
||||
@@ -32,23 +33,8 @@ export class Service {
|
||||
) {}
|
||||
}
|
||||
|
||||
export interface LocalisedPolicy {
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface Policy {
|
||||
// @ts-ignore: No great way to express indexed types together with other keys
|
||||
version: string;
|
||||
[lang: string]: LocalisedPolicy;
|
||||
}
|
||||
|
||||
export type Policies = {
|
||||
[policy: string]: Policy;
|
||||
};
|
||||
|
||||
export type ServicePolicyPair = {
|
||||
policies: Policies;
|
||||
policies: Terms["policies"];
|
||||
service: Service;
|
||||
};
|
||||
|
||||
@@ -58,6 +44,11 @@ export type TermsInteractionCallback = (
|
||||
extraClassNames?: string,
|
||||
) => Promise<string[]>;
|
||||
|
||||
export function pickBestPolicyLanguage(policy: Policy): InternationalisedPolicy | undefined {
|
||||
const termsLang = pickBestLanguage(Object.keys(policy).filter((k) => k !== "version"));
|
||||
return <InternationalisedPolicy>policy[termsLang];
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a flow where the user is presented with terms & conditions for some services
|
||||
*
|
||||
@@ -96,7 +87,7 @@ export async function startTermsFlow(
|
||||
* }
|
||||
*/
|
||||
|
||||
const terms: { policies: Policies }[] = await Promise.all(termsPromises);
|
||||
const terms: Terms[] = await Promise.all(termsPromises);
|
||||
const policiesAndServicePairs = terms.map((t, i) => {
|
||||
return { service: services[i], policies: t.policies };
|
||||
});
|
||||
@@ -113,11 +104,11 @@ export async function startTermsFlow(
|
||||
// things they've not agreed to yet.
|
||||
const unagreedPoliciesAndServicePairs: ServicePolicyPair[] = [];
|
||||
for (const { service, policies } of policiesAndServicePairs) {
|
||||
const unagreedPolicies: Policies = {};
|
||||
const unagreedPolicies: Terms["policies"] = {};
|
||||
for (const [policyName, policy] of Object.entries(policies)) {
|
||||
let policyAgreed = false;
|
||||
for (const lang of Object.keys(policy)) {
|
||||
if (lang === "version") continue;
|
||||
if (lang === "version" || typeof policy[lang] === "string") continue;
|
||||
if (agreedUrlSet.has(policy[lang].url)) {
|
||||
policyAgreed = true;
|
||||
break;
|
||||
@@ -154,7 +145,7 @@ export async function startTermsFlow(
|
||||
const urlsForService = Array.from(agreedUrlSet).filter((url) => {
|
||||
for (const policy of Object.values(policiesAndService.policies)) {
|
||||
for (const lang of Object.keys(policy)) {
|
||||
if (lang === "version") continue;
|
||||
if (lang === "version" || typeof policy[lang] === "string") continue;
|
||||
if (policy[lang].url === url) return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import classNames from "classnames";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { InternationalisedPolicy, Terms, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { AuthType, AuthDict, IInputs, IStageStatus } from "matrix-js-sdk/src/interactive-auth";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import React, { ChangeEvent, createRef, FormEvent, Fragment } from "react";
|
||||
@@ -16,14 +16,13 @@ import PopOutIcon from "@vector-im/compound-design-tokens/assets/web/icons/pop-o
|
||||
|
||||
import EmailPromptIcon from "../../../../res/img/element-icons/email-prompt.svg";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { LocalisedPolicy, Policies } from "../../../Terms";
|
||||
import { AuthHeaderModifier } from "../../structures/auth/header/AuthHeaderModifier";
|
||||
import AccessibleButton, { AccessibleButtonKind, ButtonEvent } from "../elements/AccessibleButton";
|
||||
import Field from "../elements/Field";
|
||||
import Spinner from "../elements/Spinner";
|
||||
import CaptchaForm from "./CaptchaForm";
|
||||
import { Flex } from "../../utils/Flex";
|
||||
import { pickBestPolicyLanguage } from "../../../Terms.ts";
|
||||
|
||||
/* This file contains a collection of components which are used by the
|
||||
* InteractiveAuth to prompt the user to enter the information needed
|
||||
@@ -235,12 +234,10 @@ export class RecaptchaAuthEntry extends React.Component<IRecaptchaAuthEntryProps
|
||||
}
|
||||
|
||||
interface ITermsAuthEntryProps extends IAuthEntryProps {
|
||||
stageParams?: {
|
||||
policies?: Policies;
|
||||
};
|
||||
stageParams?: Partial<Terms>;
|
||||
}
|
||||
|
||||
interface LocalisedPolicyWithId extends LocalisedPolicy {
|
||||
interface LocalisedPolicyWithId extends InternationalisedPolicy {
|
||||
id: string;
|
||||
}
|
||||
|
||||
@@ -278,7 +275,6 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
|
||||
// }
|
||||
|
||||
const allPolicies = this.props.stageParams?.policies || {};
|
||||
const prefLang = SettingsStore.getValue("language");
|
||||
const initToggles: Record<string, boolean> = {};
|
||||
const pickedPolicies: {
|
||||
id: string;
|
||||
@@ -287,17 +283,7 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
|
||||
}[] = [];
|
||||
for (const policyId of Object.keys(allPolicies)) {
|
||||
const policy = allPolicies[policyId];
|
||||
|
||||
// Pick a language based on the user's language, falling back to english,
|
||||
// and finally to the first language available. If there's still no policy
|
||||
// available then the homeserver isn't respecting the spec.
|
||||
let langPolicy: LocalisedPolicy | undefined = policy[prefLang];
|
||||
if (!langPolicy) langPolicy = policy["en"];
|
||||
if (!langPolicy) {
|
||||
// last resort
|
||||
const firstLang = Object.keys(policy).find((e) => e !== "version");
|
||||
langPolicy = firstLang ? policy[firstLang] : undefined;
|
||||
}
|
||||
const langPolicy = pickBestPolicyLanguage(policy);
|
||||
if (!langPolicy) throw new Error("Failed to find a policy to show the user");
|
||||
|
||||
initToggles[policyId] = false;
|
||||
|
||||
@@ -9,10 +9,10 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React from "react";
|
||||
import { SERVICE_TYPES } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { _t, pickBestLanguage } from "../../../languageHandler";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import { ServicePolicyPair } from "../../../Terms";
|
||||
import { pickBestPolicyLanguage, ServicePolicyPair } from "../../../Terms";
|
||||
import ExternalLink from "../elements/ExternalLink";
|
||||
import { parseUrl } from "../../../utils/UrlUtils";
|
||||
|
||||
@@ -126,8 +126,8 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
|
||||
|
||||
const policyValues = Object.values(policiesAndService.policies);
|
||||
for (let i = 0; i < policyValues.length; ++i) {
|
||||
const termDoc = policyValues[i];
|
||||
const termsLang = pickBestLanguage(Object.keys(termDoc).filter((k) => k !== "version"));
|
||||
const internationalisedPolicy = pickBestPolicyLanguage(policyValues[i]);
|
||||
if (!internationalisedPolicy) continue;
|
||||
let serviceName: JSX.Element | undefined;
|
||||
let summary: JSX.Element | undefined;
|
||||
if (i === 0) {
|
||||
@@ -136,19 +136,19 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
|
||||
}
|
||||
|
||||
rows.push(
|
||||
<tr key={termDoc[termsLang].url}>
|
||||
<tr key={internationalisedPolicy.url}>
|
||||
<td className="mx_TermsDialog_service">{serviceName}</td>
|
||||
<td className="mx_TermsDialog_summary">{summary}</td>
|
||||
<td>
|
||||
<ExternalLink rel="noreferrer noopener" target="_blank" href={termDoc[termsLang].url}>
|
||||
{termDoc[termsLang].name}
|
||||
<ExternalLink rel="noreferrer noopener" target="_blank" href={internationalisedPolicy.url}>
|
||||
{internationalisedPolicy.name}
|
||||
</ExternalLink>
|
||||
</td>
|
||||
<td>
|
||||
<TermsCheckbox
|
||||
url={termDoc[termsLang].url}
|
||||
url={internationalisedPolicy.url}
|
||||
onChange={this.onTermsCheckboxChange}
|
||||
checked={Boolean(this.state.agreedUrls[termDoc[termsLang].url])}
|
||||
checked={Boolean(this.state.agreedUrls[internationalisedPolicy.url])}
|
||||
/>
|
||||
</td>
|
||||
</tr>,
|
||||
@@ -164,7 +164,7 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
|
||||
for (const terms of Object.values(policiesAndService.policies)) {
|
||||
let docAgreed = false;
|
||||
for (const lang of Object.keys(terms)) {
|
||||
if (lang === "version") continue;
|
||||
if (lang === "version" || typeof terms[lang] === "string") continue;
|
||||
if (this.state.agreedUrls[terms[lang].url]) {
|
||||
docAgreed = true;
|
||||
break;
|
||||
|
||||
@@ -58,7 +58,7 @@ export const DiscoverySettings: React.FC = () => {
|
||||
agreedUrls: null, // From the startTermsFlow callback
|
||||
resolve: null, // Promise resolve function for startTermsFlow callback
|
||||
});
|
||||
const [hasTerms, setHasTerms] = useState<boolean>(false);
|
||||
const [mustAgreeToTerms, setMustAgreeToTerms] = useState<boolean>(false);
|
||||
|
||||
const getThreepidState = useCallback(async () => {
|
||||
setIsLoadingThreepids(true);
|
||||
@@ -103,7 +103,7 @@ export const DiscoverySettings: React.FC = () => {
|
||||
(policiesAndServices, agreedUrls, extraClassNames) => {
|
||||
return new Promise((resolve) => {
|
||||
setIdServerName(abbreviateUrl(idServerUrl));
|
||||
setHasTerms(true);
|
||||
setMustAgreeToTerms(true);
|
||||
setRequiredPolicyInfo({
|
||||
policiesAndServices,
|
||||
agreedUrls,
|
||||
@@ -113,7 +113,7 @@ export const DiscoverySettings: React.FC = () => {
|
||||
},
|
||||
);
|
||||
// User accepted all terms
|
||||
setHasTerms(false);
|
||||
setMustAgreeToTerms(false);
|
||||
} catch (e) {
|
||||
logger.warn(
|
||||
`Unable to reach identity server at ${idServerUrl} to check ` + `for terms in Settings`,
|
||||
@@ -126,7 +126,7 @@ export const DiscoverySettings: React.FC = () => {
|
||||
|
||||
if (!SettingsStore.getValue(UIFeature.ThirdPartyID)) return null;
|
||||
|
||||
if (hasTerms && requiredPolicyInfo.policiesAndServices) {
|
||||
if (mustAgreeToTerms && requiredPolicyInfo.policiesAndServices) {
|
||||
const intro = (
|
||||
<Alert type="info" title={_t("settings|general|discovery_needs_terms_title")}>
|
||||
{_t("settings|general|discovery_needs_terms", { serverName: idServerName })}
|
||||
@@ -160,7 +160,7 @@ export const DiscoverySettings: React.FC = () => {
|
||||
medium={ThreepidMedium.Email}
|
||||
threepids={emails}
|
||||
onChange={getThreepidState}
|
||||
disabled={!hasTerms}
|
||||
disabled={mustAgreeToTerms}
|
||||
isLoading={isLoadingThreepids}
|
||||
/>
|
||||
</SettingsSubsection>
|
||||
@@ -174,7 +174,7 @@ export const DiscoverySettings: React.FC = () => {
|
||||
medium={ThreepidMedium.Phone}
|
||||
threepids={phoneNumbers}
|
||||
onChange={getThreepidState}
|
||||
disabled={!hasTerms}
|
||||
disabled={mustAgreeToTerms}
|
||||
isLoading={isLoadingThreepids}
|
||||
/>
|
||||
</SettingsSubsection>
|
||||
|
||||
@@ -8,11 +8,11 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { _t, pickBestLanguage } from "../../../languageHandler";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { objectClone } from "../../../utils/objects";
|
||||
import StyledCheckbox from "../elements/StyledCheckbox";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { ServicePolicyPair } from "../../../Terms";
|
||||
import { pickBestPolicyLanguage, ServicePolicyPair } from "../../../Terms";
|
||||
|
||||
interface IProps {
|
||||
policiesAndServicePairs: ServicePolicyPair[];
|
||||
@@ -47,11 +47,12 @@ export default class InlineTermsAgreement extends React.Component<IProps, IState
|
||||
for (const servicePolicies of this.props.policiesAndServicePairs) {
|
||||
const availablePolicies = Object.values(servicePolicies.policies);
|
||||
for (const policy of availablePolicies) {
|
||||
const language = pickBestLanguage(Object.keys(policy).filter((p) => p !== "version"));
|
||||
const internationalisedPolicy = pickBestPolicyLanguage(policy);
|
||||
if (!internationalisedPolicy) continue;
|
||||
const renderablePolicy: Policy = {
|
||||
checked: false,
|
||||
url: policy[language].url,
|
||||
name: policy[language].name,
|
||||
url: internationalisedPolicy.url,
|
||||
name: internationalisedPolicy.name,
|
||||
};
|
||||
policies.push(renderablePolicy);
|
||||
}
|
||||
|
||||
@@ -6,11 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { SERVICE_TYPES, HTTPError, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { SERVICE_TYPES, HTTPError, MatrixClient, Terms } from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import SdkConfig from "../SdkConfig";
|
||||
import { Policies } from "../Terms";
|
||||
|
||||
export function getDefaultIdentityServerUrl(): string | undefined {
|
||||
return SdkConfig.get("validated_server_config")?.isUrl;
|
||||
@@ -25,7 +24,7 @@ export function setToDefaultIdentityServer(matrixClient: MatrixClient): void {
|
||||
}
|
||||
|
||||
export async function doesIdentityServerHaveTerms(matrixClient: MatrixClient, fullUrl: string): Promise<boolean> {
|
||||
let terms: { policies?: Policies } | null;
|
||||
let terms: Partial<Terms> | null;
|
||||
try {
|
||||
terms = await matrixClient.getTerms(SERVICE_TYPES.IS, fullUrl);
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user