Device manager - verify other devices (PSG-724) (#9274)
* trigger verification of other devices * tests * fix strict errors * add types
This commit is contained in:
parent
236ca2e494
commit
4623d84dd0
6 changed files with 181 additions and 15 deletions
|
@ -24,6 +24,7 @@ import { DeviceWithVerification } from './types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
device: DeviceWithVerification;
|
device: DeviceWithVerification;
|
||||||
|
onVerifyDevice?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MetadataTable {
|
interface MetadataTable {
|
||||||
|
@ -31,7 +32,10 @@ interface MetadataTable {
|
||||||
values: { label: string, value?: string | React.ReactNode }[];
|
values: { label: string, value?: string | React.ReactNode }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const DeviceDetails: React.FC<Props> = ({ device }) => {
|
const DeviceDetails: React.FC<Props> = ({
|
||||||
|
device,
|
||||||
|
onVerifyDevice,
|
||||||
|
}) => {
|
||||||
const metadata: MetadataTable[] = [
|
const metadata: MetadataTable[] = [
|
||||||
{
|
{
|
||||||
values: [
|
values: [
|
||||||
|
@ -52,7 +56,10 @@ const DeviceDetails: React.FC<Props> = ({ device }) => {
|
||||||
return <div className='mx_DeviceDetails' data-testid={`device-detail-${device.device_id}`}>
|
return <div className='mx_DeviceDetails' data-testid={`device-detail-${device.device_id}`}>
|
||||||
<section className='mx_DeviceDetails_section'>
|
<section className='mx_DeviceDetails_section'>
|
||||||
<Heading size='h3'>{ device.display_name ?? device.device_id }</Heading>
|
<Heading size='h3'>{ device.display_name ?? device.device_id }</Heading>
|
||||||
<DeviceVerificationStatusCard device={device} />
|
<DeviceVerificationStatusCard
|
||||||
|
device={device}
|
||||||
|
onVerifyDevice={onVerifyDevice}
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
<section className='mx_DeviceDetails_section'>
|
<section className='mx_DeviceDetails_section'>
|
||||||
<p className='mx_DeviceDetails_sectionHeading'>{ _t('Session details') }</p>
|
<p className='mx_DeviceDetails_sectionHeading'>{ _t('Session details') }</p>
|
||||||
|
|
|
@ -39,6 +39,7 @@ interface Props {
|
||||||
filter?: DeviceSecurityVariation;
|
filter?: DeviceSecurityVariation;
|
||||||
onFilterChange: (filter: DeviceSecurityVariation | undefined) => void;
|
onFilterChange: (filter: DeviceSecurityVariation | undefined) => void;
|
||||||
onDeviceExpandToggle: (deviceId: DeviceWithVerification['device_id']) => void;
|
onDeviceExpandToggle: (deviceId: DeviceWithVerification['device_id']) => void;
|
||||||
|
onRequestDeviceVerification?: (deviceId: DeviceWithVerification['device_id']) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// devices without timestamp metadata should be sorted last
|
// devices without timestamp metadata should be sorted last
|
||||||
|
@ -132,8 +133,10 @@ const DeviceListItem: React.FC<{
|
||||||
device: DeviceWithVerification;
|
device: DeviceWithVerification;
|
||||||
isExpanded: boolean;
|
isExpanded: boolean;
|
||||||
onDeviceExpandToggle: () => void;
|
onDeviceExpandToggle: () => void;
|
||||||
|
onRequestDeviceVerification?: () => void;
|
||||||
}> = ({
|
}> = ({
|
||||||
device, isExpanded, onDeviceExpandToggle,
|
device, isExpanded, onDeviceExpandToggle,
|
||||||
|
onRequestDeviceVerification,
|
||||||
}) => <li className='mx_FilteredDeviceList_listItem'>
|
}) => <li className='mx_FilteredDeviceList_listItem'>
|
||||||
<DeviceTile
|
<DeviceTile
|
||||||
device={device}
|
device={device}
|
||||||
|
@ -143,7 +146,7 @@ const DeviceListItem: React.FC<{
|
||||||
onClick={onDeviceExpandToggle}
|
onClick={onDeviceExpandToggle}
|
||||||
/>
|
/>
|
||||||
</DeviceTile>
|
</DeviceTile>
|
||||||
{ isExpanded && <DeviceDetails device={device} /> }
|
{ isExpanded && <DeviceDetails device={device} onVerifyDevice={onRequestDeviceVerification} /> }
|
||||||
</li>;
|
</li>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -157,6 +160,7 @@ export const FilteredDeviceList =
|
||||||
expandedDeviceIds,
|
expandedDeviceIds,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
onDeviceExpandToggle,
|
onDeviceExpandToggle,
|
||||||
|
onRequestDeviceVerification,
|
||||||
}: Props, ref: ForwardedRef<HTMLDivElement>) => {
|
}: Props, ref: ForwardedRef<HTMLDivElement>) => {
|
||||||
const sortedDevices = getFilteredSortedDevices(devices, filter);
|
const sortedDevices = getFilteredSortedDevices(devices, filter);
|
||||||
|
|
||||||
|
@ -210,6 +214,11 @@ export const FilteredDeviceList =
|
||||||
device={device}
|
device={device}
|
||||||
isExpanded={expandedDeviceIds.includes(device.device_id)}
|
isExpanded={expandedDeviceIds.includes(device.device_id)}
|
||||||
onDeviceExpandToggle={() => onDeviceExpandToggle(device.device_id)}
|
onDeviceExpandToggle={() => onDeviceExpandToggle(device.device_id)}
|
||||||
|
onRequestDeviceVerification={
|
||||||
|
onRequestDeviceVerification
|
||||||
|
? () => onRequestDeviceVerification(device.device_id)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>,
|
/>,
|
||||||
) }
|
) }
|
||||||
</ol>
|
</ol>
|
||||||
|
|
|
@ -17,10 +17,13 @@ limitations under the License.
|
||||||
import { useCallback, useContext, useEffect, useState } from "react";
|
import { useCallback, useContext, useEffect, useState } from "react";
|
||||||
import { IMyDevice, MatrixClient } from "matrix-js-sdk/src/matrix";
|
import { IMyDevice, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
import { CrossSigningInfo } from "matrix-js-sdk/src/crypto/CrossSigning";
|
import { CrossSigningInfo } from "matrix-js-sdk/src/crypto/CrossSigning";
|
||||||
|
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||||
|
import { User } from "matrix-js-sdk/src/models/user";
|
||||||
|
import { MatrixError } from "matrix-js-sdk/src/matrix";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import MatrixClientContext from "../../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../../contexts/MatrixClientContext";
|
||||||
import { DevicesDictionary } from "./types";
|
import { DevicesDictionary, DeviceWithVerification } from "./types";
|
||||||
|
|
||||||
const isDeviceVerified = (
|
const isDeviceVerified = (
|
||||||
matrixClient: MatrixClient,
|
matrixClient: MatrixClient,
|
||||||
|
@ -28,7 +31,14 @@ const isDeviceVerified = (
|
||||||
device: IMyDevice,
|
device: IMyDevice,
|
||||||
): boolean | null => {
|
): boolean | null => {
|
||||||
try {
|
try {
|
||||||
const deviceInfo = matrixClient.getStoredDevice(matrixClient.getUserId(), device.device_id);
|
const userId = matrixClient.getUserId();
|
||||||
|
if (!userId) {
|
||||||
|
throw new Error('No user id');
|
||||||
|
}
|
||||||
|
const deviceInfo = matrixClient.getStoredDevice(userId, device.device_id);
|
||||||
|
if (!deviceInfo) {
|
||||||
|
throw new Error('No device info available');
|
||||||
|
}
|
||||||
return crossSigningInfo.checkDeviceTrust(
|
return crossSigningInfo.checkDeviceTrust(
|
||||||
crossSigningInfo,
|
crossSigningInfo,
|
||||||
deviceInfo,
|
deviceInfo,
|
||||||
|
@ -41,9 +51,13 @@ const isDeviceVerified = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchDevicesWithVerification = async (matrixClient: MatrixClient): Promise<DevicesState['devices']> => {
|
const fetchDevicesWithVerification = async (
|
||||||
|
matrixClient: MatrixClient,
|
||||||
|
userId: string,
|
||||||
|
): Promise<DevicesState['devices']> => {
|
||||||
const { devices } = await matrixClient.getDevices();
|
const { devices } = await matrixClient.getDevices();
|
||||||
const crossSigningInfo = matrixClient.getStoredCrossSigningForUser(matrixClient.getUserId());
|
|
||||||
|
const crossSigningInfo = matrixClient.getStoredCrossSigningForUser(userId);
|
||||||
|
|
||||||
const devicesDict = devices.reduce((acc, device: IMyDevice) => ({
|
const devicesDict = devices.reduce((acc, device: IMyDevice) => ({
|
||||||
...acc,
|
...acc,
|
||||||
|
@ -63,7 +77,10 @@ export enum OwnDevicesError {
|
||||||
type DevicesState = {
|
type DevicesState = {
|
||||||
devices: DevicesDictionary;
|
devices: DevicesDictionary;
|
||||||
currentDeviceId: string;
|
currentDeviceId: string;
|
||||||
|
currentUserMember?: User;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
// not provided when current session cannot request verification
|
||||||
|
requestDeviceVerification?: (deviceId: DeviceWithVerification['device_id']) => Promise<VerificationRequest>;
|
||||||
refreshDevices: () => Promise<void>;
|
refreshDevices: () => Promise<void>;
|
||||||
error?: OwnDevicesError;
|
error?: OwnDevicesError;
|
||||||
};
|
};
|
||||||
|
@ -71,6 +88,7 @@ export const useOwnDevices = (): DevicesState => {
|
||||||
const matrixClient = useContext(MatrixClientContext);
|
const matrixClient = useContext(MatrixClientContext);
|
||||||
|
|
||||||
const currentDeviceId = matrixClient.getDeviceId();
|
const currentDeviceId = matrixClient.getDeviceId();
|
||||||
|
const userId = matrixClient.getUserId();
|
||||||
|
|
||||||
const [devices, setDevices] = useState<DevicesState['devices']>({});
|
const [devices, setDevices] = useState<DevicesState['devices']>({});
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
@ -79,11 +97,16 @@ export const useOwnDevices = (): DevicesState => {
|
||||||
const refreshDevices = useCallback(async () => {
|
const refreshDevices = useCallback(async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const devices = await fetchDevicesWithVerification(matrixClient);
|
// realistically we should never hit this
|
||||||
|
// but it satisfies types
|
||||||
|
if (!userId) {
|
||||||
|
throw new Error('Cannot fetch devices without user id');
|
||||||
|
}
|
||||||
|
const devices = await fetchDevicesWithVerification(matrixClient, userId);
|
||||||
setDevices(devices);
|
setDevices(devices);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.httpStatus == 404) {
|
if ((error as MatrixError).httpStatus == 404) {
|
||||||
// 404 probably means the HS doesn't yet support the API.
|
// 404 probably means the HS doesn't yet support the API.
|
||||||
setError(OwnDevicesError.Unsupported);
|
setError(OwnDevicesError.Unsupported);
|
||||||
} else {
|
} else {
|
||||||
|
@ -92,15 +115,28 @@ export const useOwnDevices = (): DevicesState => {
|
||||||
}
|
}
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}, [matrixClient]);
|
}, [matrixClient, userId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refreshDevices();
|
refreshDevices();
|
||||||
}, [refreshDevices]);
|
}, [refreshDevices]);
|
||||||
|
|
||||||
|
const isCurrentDeviceVerified = !!devices[currentDeviceId]?.isVerified;
|
||||||
|
|
||||||
|
const requestDeviceVerification = isCurrentDeviceVerified && userId
|
||||||
|
? async (deviceId: DeviceWithVerification['device_id']) => {
|
||||||
|
return await matrixClient.requestVerification(
|
||||||
|
userId,
|
||||||
|
[deviceId],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
devices,
|
devices,
|
||||||
currentDeviceId,
|
currentDeviceId,
|
||||||
|
currentUserMember: userId && matrixClient.getUser(userId) || undefined,
|
||||||
|
requestDeviceVerification,
|
||||||
refreshDevices,
|
refreshDevices,
|
||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { _t } from "../../../../../languageHandler";
|
import { _t } from "../../../../../languageHandler";
|
||||||
import { useOwnDevices } from '../../devices/useOwnDevices';
|
import { useOwnDevices } from '../../devices/useOwnDevices';
|
||||||
|
@ -26,12 +26,15 @@ import { DeviceSecurityVariation, DeviceWithVerification } from '../../devices/t
|
||||||
import SettingsTab from '../SettingsTab';
|
import SettingsTab from '../SettingsTab';
|
||||||
import Modal from '../../../../../Modal';
|
import Modal from '../../../../../Modal';
|
||||||
import SetupEncryptionDialog from '../../../dialogs/security/SetupEncryptionDialog';
|
import SetupEncryptionDialog from '../../../dialogs/security/SetupEncryptionDialog';
|
||||||
|
import VerificationRequestDialog from '../../../dialogs/VerificationRequestDialog';
|
||||||
|
|
||||||
const SessionManagerTab: React.FC = () => {
|
const SessionManagerTab: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
devices,
|
devices,
|
||||||
currentDeviceId,
|
currentDeviceId,
|
||||||
|
currentUserMember,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
requestDeviceVerification,
|
||||||
refreshDevices,
|
refreshDevices,
|
||||||
} = useOwnDevices();
|
} = useOwnDevices();
|
||||||
const [filter, setFilter] = useState<DeviceSecurityVariation>();
|
const [filter, setFilter] = useState<DeviceSecurityVariation>();
|
||||||
|
@ -65,15 +68,28 @@ const SessionManagerTab: React.FC = () => {
|
||||||
const shouldShowOtherSessions = Object.keys(otherDevices).length > 0;
|
const shouldShowOtherSessions = Object.keys(otherDevices).length > 0;
|
||||||
|
|
||||||
const onVerifyCurrentDevice = () => {
|
const onVerifyCurrentDevice = () => {
|
||||||
if (!currentDevice) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Modal.createDialog(
|
Modal.createDialog(
|
||||||
SetupEncryptionDialog as unknown as React.ComponentType,
|
SetupEncryptionDialog as unknown as React.ComponentType,
|
||||||
{ onFinished: refreshDevices },
|
{ onFinished: refreshDevices },
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onTriggerDeviceVerification = useCallback((deviceId: DeviceWithVerification['device_id']) => {
|
||||||
|
if (!requestDeviceVerification) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const verificationRequestPromise = requestDeviceVerification(deviceId);
|
||||||
|
Modal.createDialog(VerificationRequestDialog, {
|
||||||
|
verificationRequestPromise,
|
||||||
|
member: currentUserMember,
|
||||||
|
onFinished: async () => {
|
||||||
|
const request = await verificationRequestPromise;
|
||||||
|
request.cancel();
|
||||||
|
await refreshDevices();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [requestDeviceVerification, refreshDevices, currentUserMember]);
|
||||||
|
|
||||||
useEffect(() => () => {
|
useEffect(() => () => {
|
||||||
clearTimeout(scrollIntoViewTimeoutRef.current);
|
clearTimeout(scrollIntoViewTimeoutRef.current);
|
||||||
}, [scrollIntoViewTimeoutRef]);
|
}, [scrollIntoViewTimeoutRef]);
|
||||||
|
@ -105,6 +121,7 @@ const SessionManagerTab: React.FC = () => {
|
||||||
expandedDeviceIds={expandedDeviceIds}
|
expandedDeviceIds={expandedDeviceIds}
|
||||||
onFilterChange={setFilter}
|
onFilterChange={setFilter}
|
||||||
onDeviceExpandToggle={onDeviceExpandToggle}
|
onDeviceExpandToggle={onDeviceExpandToggle}
|
||||||
|
onRequestDeviceVerification={requestDeviceVerification ? onTriggerDeviceVerification : undefined}
|
||||||
ref={filteredDeviceListRef}
|
ref={filteredDeviceListRef}
|
||||||
/>
|
/>
|
||||||
</SettingsSubsection>
|
</SettingsSubsection>
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { act } from 'react-dom/test-utils';
|
||||||
import { DeviceInfo } from 'matrix-js-sdk/src/crypto/deviceinfo';
|
import { DeviceInfo } from 'matrix-js-sdk/src/crypto/deviceinfo';
|
||||||
import { logger } from 'matrix-js-sdk/src/logger';
|
import { logger } from 'matrix-js-sdk/src/logger';
|
||||||
import { DeviceTrustLevel } from 'matrix-js-sdk/src/crypto/CrossSigning';
|
import { DeviceTrustLevel } from 'matrix-js-sdk/src/crypto/CrossSigning';
|
||||||
|
import { VerificationRequest } from 'matrix-js-sdk/src/crypto/verification/request/VerificationRequest';
|
||||||
|
|
||||||
import SessionManagerTab from '../../../../../../src/components/views/settings/tabs/user/SessionManagerTab';
|
import SessionManagerTab from '../../../../../../src/components/views/settings/tabs/user/SessionManagerTab';
|
||||||
import MatrixClientContext from '../../../../../../src/contexts/MatrixClientContext';
|
import MatrixClientContext from '../../../../../../src/contexts/MatrixClientContext';
|
||||||
|
@ -52,12 +53,14 @@ describe('<SessionManagerTab />', () => {
|
||||||
const mockCrossSigningInfo = {
|
const mockCrossSigningInfo = {
|
||||||
checkDeviceTrust: jest.fn(),
|
checkDeviceTrust: jest.fn(),
|
||||||
};
|
};
|
||||||
|
const mockVerificationRequest = { cancel: jest.fn() } as unknown as VerificationRequest;
|
||||||
const mockClient = getMockClientWithEventEmitter({
|
const mockClient = getMockClientWithEventEmitter({
|
||||||
...mockClientMethodsUser(aliceId),
|
...mockClientMethodsUser(aliceId),
|
||||||
getStoredCrossSigningForUser: jest.fn().mockReturnValue(mockCrossSigningInfo),
|
getStoredCrossSigningForUser: jest.fn().mockReturnValue(mockCrossSigningInfo),
|
||||||
getDevices: jest.fn(),
|
getDevices: jest.fn(),
|
||||||
getStoredDevice: jest.fn(),
|
getStoredDevice: jest.fn(),
|
||||||
getDeviceId: jest.fn().mockReturnValue(deviceId),
|
getDeviceId: jest.fn().mockReturnValue(deviceId),
|
||||||
|
requestVerification: jest.fn().mockResolvedValue(mockVerificationRequest),
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultProps = {};
|
const defaultProps = {};
|
||||||
|
@ -278,4 +281,97 @@ describe('<SessionManagerTab />', () => {
|
||||||
expect(getByTestId(`device-detail-${alicesOlderMobileDevice.device_id}`)).toBeTruthy();
|
expect(getByTestId(`device-detail-${alicesOlderMobileDevice.device_id}`)).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Device verification', () => {
|
||||||
|
it('does not render device verification cta when current session is not verified', async () => {
|
||||||
|
mockClient.getDevices.mockResolvedValue({
|
||||||
|
devices: [alicesDevice, alicesOlderMobileDevice, alicesMobileDevice],
|
||||||
|
});
|
||||||
|
const { getByTestId, queryByTestId } = render(getComponent());
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await flushPromisesWithFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
const tile1 = getByTestId(`device-tile-${alicesOlderMobileDevice.device_id}`);
|
||||||
|
const toggle1 = tile1.querySelector('[aria-label="Toggle device details"]') as Element;
|
||||||
|
fireEvent.click(toggle1);
|
||||||
|
|
||||||
|
// verify device button is not rendered
|
||||||
|
expect(queryByTestId(`verification-status-button-${alicesOlderMobileDevice.device_id}`)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders device verification cta on other sessions when current session is verified', async () => {
|
||||||
|
const modalSpy = jest.spyOn(Modal, 'createDialog');
|
||||||
|
|
||||||
|
// make the current device verified
|
||||||
|
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
|
||||||
|
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
||||||
|
mockCrossSigningInfo.checkDeviceTrust
|
||||||
|
.mockImplementation((_userId, { deviceId }) => {
|
||||||
|
console.log('hhh', deviceId);
|
||||||
|
if (deviceId === alicesDevice.device_id) {
|
||||||
|
return new DeviceTrustLevel(true, true, false, false);
|
||||||
|
}
|
||||||
|
throw new Error('everything else unverified');
|
||||||
|
});
|
||||||
|
|
||||||
|
const { getByTestId } = render(getComponent());
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await flushPromisesWithFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
const tile1 = getByTestId(`device-tile-${alicesMobileDevice.device_id}`);
|
||||||
|
const toggle1 = tile1.querySelector('[aria-label="Toggle device details"]') as Element;
|
||||||
|
fireEvent.click(toggle1);
|
||||||
|
|
||||||
|
// click verify button from current session section
|
||||||
|
fireEvent.click(getByTestId(`verification-status-button-${alicesMobileDevice.device_id}`));
|
||||||
|
|
||||||
|
expect(mockClient.requestVerification).toHaveBeenCalledWith(aliceId, [alicesMobileDevice.device_id]);
|
||||||
|
expect(modalSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('refreshes devices after verifying other device', async () => {
|
||||||
|
const modalSpy = jest.spyOn(Modal, 'createDialog');
|
||||||
|
|
||||||
|
// make the current device verified
|
||||||
|
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
|
||||||
|
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
||||||
|
mockCrossSigningInfo.checkDeviceTrust
|
||||||
|
.mockImplementation((_userId, { deviceId }) => {
|
||||||
|
console.log('hhh', deviceId);
|
||||||
|
if (deviceId === alicesDevice.device_id) {
|
||||||
|
return new DeviceTrustLevel(true, true, false, false);
|
||||||
|
}
|
||||||
|
throw new Error('everything else unverified');
|
||||||
|
});
|
||||||
|
|
||||||
|
const { getByTestId } = render(getComponent());
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await flushPromisesWithFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
const tile1 = getByTestId(`device-tile-${alicesMobileDevice.device_id}`);
|
||||||
|
const toggle1 = tile1.querySelector('[aria-label="Toggle device details"]') as Element;
|
||||||
|
fireEvent.click(toggle1);
|
||||||
|
|
||||||
|
// reset mock counter before triggering verification
|
||||||
|
mockClient.getDevices.mockClear();
|
||||||
|
|
||||||
|
// click verify button from current session section
|
||||||
|
fireEvent.click(getByTestId(`verification-status-button-${alicesMobileDevice.device_id}`));
|
||||||
|
|
||||||
|
const { onFinished: modalOnFinished } = modalSpy.mock.calls[0][1] as any;
|
||||||
|
// simulate modal completing process
|
||||||
|
await modalOnFinished();
|
||||||
|
|
||||||
|
// cancelled in case it was a failure exit from modal
|
||||||
|
expect(mockVerificationRequest.cancel).toHaveBeenCalled();
|
||||||
|
// devices refreshed
|
||||||
|
expect(mockClient.getDevices).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import { MethodKeysOf, mocked, MockedObject } from "jest-mock";
|
import { MethodKeysOf, mocked, MockedObject } from "jest-mock";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
import { MatrixClient, User } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
|
||||||
|
|
||||||
|
@ -65,6 +65,7 @@ export const unmockClientPeg = () => jest.spyOn(MatrixClientPeg, 'get').mockRest
|
||||||
*/
|
*/
|
||||||
export const mockClientMethodsUser = (userId = '@alice:domain') => ({
|
export const mockClientMethodsUser = (userId = '@alice:domain') => ({
|
||||||
getUserId: jest.fn().mockReturnValue(userId),
|
getUserId: jest.fn().mockReturnValue(userId),
|
||||||
|
getUser: jest.fn().mockReturnValue(new User(userId)),
|
||||||
isGuest: jest.fn().mockReturnValue(false),
|
isGuest: jest.fn().mockReturnValue(false),
|
||||||
mxcUrlToHttp: jest.fn().mockReturnValue('mock-mxcUrlToHttp'),
|
mxcUrlToHttp: jest.fn().mockReturnValue('mock-mxcUrlToHttp'),
|
||||||
credentials: { userId },
|
credentials: { userId },
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue