/* Copyright 2024 New Vector Ltd. Copyright 2019 Vector Creations 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 from "react"; import { Device } from "matrix-js-sdk/src/matrix"; import { GeneratedSas, EmojiMapping } from "matrix-js-sdk/src/crypto-api"; import SasEmoji from "@matrix-org/spec/sas-emoji.json"; import { _t, getNormalizedLanguageKeys, getUserLanguage } from "../../../languageHandler"; import { PendingActionSpinner } from "../right_panel/EncryptionInfo"; import AccessibleButton from "../elements/AccessibleButton"; interface IProps { pending?: boolean; displayName?: string; // required if pending is true /** Details of the other device involved in the verification, if known */ otherDeviceDetails?: Device; onDone: () => void; onCancel: () => void; sas: GeneratedSas; isSelf?: boolean; inDialog?: boolean; // whether this component is being shown in a dialog and to use DialogButtons } interface IState { pending: boolean; cancelling?: boolean; } const SasEmojiMap = new Map< string, // lowercase { description: string; translations: { [normalizedLanguageKey: string]: string; }; } >( SasEmoji.map(({ description, translated_descriptions: translations }) => [ description.toLowerCase(), { description, // Normalize the translation keys translations: Object.keys(translations).reduce>((o, k) => { for (const key of getNormalizedLanguageKeys(k)) { o[key] = translations[k as keyof typeof translations]!; } return o; }, {}), }, ]), ); /** * Translate given EmojiMapping into the target locale * @param mapping - the given EmojiMapping to translate * @param locale - the BCP 47 locale to translate to, will fall back to English as the base locale for Matrix SAS Emoji. */ export function tEmoji(mapping: EmojiMapping, locale: string): string { const name = mapping[1]; const emoji = SasEmojiMap.get(name.toLowerCase()); if (!emoji) { console.warn("Emoji not found for translation", name); return name; } for (const key of getNormalizedLanguageKeys(locale)) { if (!!emoji.translations[key]) { return emoji.translations[key]; } } return emoji.description; } export default class VerificationShowSas extends React.Component { public constructor(props: IProps) { super(props); this.state = { pending: false, }; } private onMatchClick = (): void => { this.setState({ pending: true }); this.props.onDone(); }; private onDontMatchClick = (): void => { this.setState({ cancelling: true }); this.props.onCancel(); }; public render(): React.ReactNode { const locale = getUserLanguage(); let sasDisplay; let sasCaption; if (this.props.sas.emoji) { const emojiBlocks = this.props.sas.emoji.map((emoji, i) => (
{emoji[0]}
{tEmoji(emoji, locale)}
)); sasDisplay = (
{emojiBlocks.slice(0, 4)}
{emojiBlocks.slice(4)}
); sasCaption = this.props.isSelf ? _t("encryption|verification|sas_emoji_caption_self") : _t("encryption|verification|sas_emoji_caption_user"); } else if (this.props.sas.decimal) { const numberBlocks = this.props.sas.decimal.map((num, i) => {num}); sasDisplay =
{numberBlocks}
; sasCaption = this.props.isSelf ? _t("encryption|verification|sas_caption_self") : _t("encryption|verification|sas_caption_user"); } else { return (
{_t("encryption|verification|unsupported_method")} {_t("action|cancel")}
); } let confirm; if (this.state.pending && this.props.isSelf) { let text; // device shouldn't be null in this situation but it can be, eg. if the device is // logged out during verification const otherDevice = this.props.otherDeviceDetails; if (otherDevice) { text = _t("encryption|verification|waiting_other_device_details", { deviceName: otherDevice.displayName, deviceId: otherDevice.deviceId, }); } else { text = _t("encryption|verification|waiting_other_device"); } confirm =

{text}

; } else if (this.state.pending || this.state.cancelling) { let text; if (this.state.pending) { const { displayName } = this.props; text = _t("encryption|verification|waiting_other_user", { displayName }); } else { text = _t("encryption|verification|cancelling"); } confirm = ; } else { confirm = (
{_t("encryption|verification|sas_no_match")} {_t("encryption|verification|sas_match")}
); } return (

{sasCaption}

{sasDisplay}

{this.props.isSelf ? "" : _t("encryption|verification|in_person")}

{confirm}
); } }