Replace `getQRCodeBytes` with `generateQRCode` (#11241)

* Replace `getQRCodeBytes` with `generateQRCode`

* another test update

* remove obsolete snapshot
pull/28217/head
Richard van der Hoff 2023-07-13 14:55:55 +01:00 committed by GitHub
parent 9077592bec
commit 2cfbd73cd3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 48 additions and 12 deletions

View File

@ -22,7 +22,8 @@ import { _t } from "../../../languageHandler";
import Spinner from "./Spinner"; import Spinner from "./Spinner";
interface IProps extends QRCodeRenderersOptions { interface IProps extends QRCodeRenderersOptions {
data: string | QRCodeSegment[]; /** The data for the QR code. If `null`, a spinner is shown. */
data: null | string | QRCodeSegment[];
className?: string; className?: string;
} }
@ -33,6 +34,10 @@ const defaultOptions: QRCodeToDataURLOptions = {
const QRCode: React.FC<IProps> = ({ data, className, ...options }) => { const QRCode: React.FC<IProps> = ({ data, className, ...options }) => {
const [dataUri, setUri] = React.useState<string | null>(null); const [dataUri, setUri] = React.useState<string | null>(null);
React.useEffect(() => { React.useEffect(() => {
if (data === null) {
setUri(null);
return;
}
let cancelled = false; let cancelled = false;
toDataURL(data, { ...defaultOptions, ...options }).then((uri) => { toDataURL(data, { ...defaultOptions, ...options }).then((uri) => {
if (cancelled) return; if (cancelled) return;

View File

@ -19,14 +19,15 @@ import React from "react";
import QRCode from "../QRCode"; import QRCode from "../QRCode";
interface IProps { interface IProps {
qrCodeBytes: Buffer; /** The data for the QR code. If `undefined`, a spinner is shown. */
qrCodeBytes: undefined | Buffer;
} }
export default class VerificationQRCode extends React.PureComponent<IProps> { export default class VerificationQRCode extends React.PureComponent<IProps> {
public render(): React.ReactNode { public render(): React.ReactNode {
return ( return (
<QRCode <QRCode
data={[{ data: this.props.qrCodeBytes, mode: "byte" }]} data={this.props.qrCodeBytes === undefined ? null : [{ data: this.props.qrCodeBytes, mode: "byte" }]}
className="mx_VerificationQRCode" className="mx_VerificationQRCode"
width={196} width={196}
/> />

View File

@ -49,6 +49,14 @@ interface IProps {
} }
interface IState { interface IState {
/**
* The data for the QR code to display.
*
* We attempt to calculate this once the verification request transitions into the "Ready" phase. If the other
* side cannot scan QR codes, it will remain `undefined`.
*/
qrCodeBytes: Buffer | undefined;
sasEvent: ShowSasCallbacks | null; sasEvent: ShowSasCallbacks | null;
emojiButtonClicked?: boolean; emojiButtonClicked?: boolean;
reciprocateButtonClicked?: boolean; reciprocateButtonClicked?: boolean;
@ -68,9 +76,12 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
/** have we yet tried to check the other device's info */ /** have we yet tried to check the other device's info */
private haveCheckedDevice = false; private haveCheckedDevice = false;
/** have we yet tried to get the QR code */
private haveFetchedQRCode = false;
public constructor(props: IProps) { public constructor(props: IProps) {
super(props); super(props);
this.state = { sasEvent: null, reciprocateQREvent: null }; this.state = { qrCodeBytes: undefined, sasEvent: null, reciprocateQREvent: null };
this.hasVerifier = false; this.hasVerifier = false;
} }
@ -78,7 +89,6 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
const { member, request } = this.props; const { member, request } = this.props;
const showSAS: boolean = request.otherPartySupportsMethod(verificationMethods.SAS); const showSAS: boolean = request.otherPartySupportsMethod(verificationMethods.SAS);
const showQR: boolean = request.otherPartySupportsMethod(SCAN_QR_CODE_METHOD); const showQR: boolean = request.otherPartySupportsMethod(SCAN_QR_CODE_METHOD);
const qrCodeBytes = showQR ? request.getQRCodeBytes() : null;
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;
const noCommonMethodError: JSX.Element | null = const noCommonMethodError: JSX.Element | null =
@ -97,11 +107,11 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
// HACK: This is a terrible idea. // HACK: This is a terrible idea.
let qrBlockDialog: JSX.Element | undefined; let qrBlockDialog: JSX.Element | undefined;
let sasBlockDialog: JSX.Element | undefined; let sasBlockDialog: JSX.Element | undefined;
if (!!qrCodeBytes) { if (showQR) {
qrBlockDialog = ( qrBlockDialog = (
<div className="mx_VerificationPanel_QRPhase_startOption"> <div className="mx_VerificationPanel_QRPhase_startOption">
<p>{_t("Scan this unique code")}</p> <p>{_t("Scan this unique code")}</p>
<VerificationQRCode qrCodeBytes={qrCodeBytes} /> <VerificationQRCode qrCodeBytes={this.state.qrCodeBytes} />
</div> </div>
); );
} }
@ -145,7 +155,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
} }
let qrBlock: JSX.Element | undefined; let qrBlock: JSX.Element | undefined;
if (!!qrCodeBytes) { if (showQR) {
qrBlock = ( qrBlock = (
<div className="mx_UserInfo_container"> <div className="mx_UserInfo_container">
<h3>{_t("Verify by scanning")}</h3> <h3>{_t("Verify by scanning")}</h3>
@ -156,7 +166,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
</p> </p>
<div className="mx_VerificationPanel_qrCode"> <div className="mx_VerificationPanel_qrCode">
<VerificationQRCode qrCodeBytes={qrCodeBytes} /> <VerificationQRCode qrCodeBytes={this.state.qrCodeBytes} />
</div> </div>
</div> </div>
); );
@ -426,6 +436,20 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
// if we have a device ID and did not have one before, fetch the device's details // if we have a device ID and did not have one before, fetch the device's details
this.maybeGetOtherDevice(); this.maybeGetOtherDevice();
// if we have had a reply from the other side (ie, the phase is "ready") and we have not
// yet done so, fetch the QR code
if (request.phase === Phase.Ready && !this.haveFetchedQRCode) {
this.haveFetchedQRCode = true;
request.generateQRCode().then(
(buf) => {
this.setState({ qrCodeBytes: buf });
},
(error) => {
console.error("Error generating QR code:", error);
},
);
}
const hadVerifier = this.hasVerifier; const hadVerifier = this.hasVerifier;
this.hasVerifier = !!request.verifier; this.hasVerifier = !!request.verifier;
if (!hadVerifier && this.hasVerifier) { if (!hadVerifier && this.hasVerifier) {

View File

@ -22,6 +22,11 @@ describe("<QRCode />", () => {
cleanup(); cleanup();
}); });
it("shows a spinner when data is null", async () => {
const { container } = render(<QRCode data={null} />);
expect(container.querySelector(".mx_Spinner")).toBeDefined();
});
it("renders a QR with defaults", async () => { it("renders a QR with defaults", async () => {
const { container, getAllByAltText } = render(<QRCode data="asd" />); const { container, getAllByAltText } = render(<QRCode data="asd" />);
await waitFor(() => getAllByAltText("QR Code").length === 1); await waitFor(() => getAllByAltText("QR Code").length === 1);

View File

@ -179,6 +179,7 @@ describe("<UserInfo />", () => {
Object.assign(this, { Object.assign(this, {
channel: { transactionId: 1 }, channel: { transactionId: 1 },
otherPartySupportsMethod: jest.fn(), otherPartySupportsMethod: jest.fn(),
generateQRCode: jest.fn().mockReturnValue(new Promise(() => {})),
...opts, ...opts,
}); });
} }

View File

@ -56,7 +56,7 @@ describe("<VerificationPanel />", () => {
const request = makeMockVerificationRequest({ const request = makeMockVerificationRequest({
phase: Phase.Ready, phase: Phase.Ready,
}); });
request.getQRCodeBytes.mockReturnValue(Buffer.from("test", "utf-8")); request.generateQRCode.mockResolvedValue(Buffer.from("test", "utf-8"));
const container = renderComponent({ const container = renderComponent({
request: request, request: request,
layout: "dialog", layout: "dialog",
@ -81,7 +81,7 @@ describe("<VerificationPanel />", () => {
const request = makeMockVerificationRequest({ const request = makeMockVerificationRequest({
phase: Phase.Ready, phase: Phase.Ready,
}); });
request.getQRCodeBytes.mockReturnValue(Buffer.from("test", "utf-8")); request.generateQRCode.mockResolvedValue(Buffer.from("test", "utf-8"));
const container = renderComponent({ const container = renderComponent({
request: request, request: request,
member: new User("@other:user"), member: new User("@other:user"),
@ -198,7 +198,7 @@ function makeMockVerificationRequest(props: Partial<VerificationRequest> = {}):
Object.assign(request, { Object.assign(request, {
cancel: jest.fn(), cancel: jest.fn(),
otherPartySupportsMethod: jest.fn().mockReturnValue(true), otherPartySupportsMethod: jest.fn().mockReturnValue(true),
getQRCodeBytes: jest.fn(), generateQRCode: jest.fn().mockResolvedValue(undefined),
...props, ...props,
}); });
return request as unknown as Mocked<VerificationRequest>; return request as unknown as Mocked<VerificationRequest>;