Device manager - add foundation for extended device info (#9344)
* record device client inforamtion events on app start * matrix-client-information -> matrix_client_information * fix types * remove another unused export * add docs link * display device client information in device details * update snapshots * integration-ish test client information in metadata * tests * fix tests * export helper * DeviceClientInformation type * Device manager - select all devices (#9330) * add device selection that does nothing * multi select and sign out of sessions * test multiple selection * fix type after rebase * select all sessions * rename type * use ExtendedDevice type everywhere * rename clientName to appName for less collision with UA parser * fix bad find and replace * rename ExtendedDeviceInfo to ExtendedDeviceAppInfo * rename DeviceType comp to DeviceTypeIcon * update tests for new required property deviceType * add stubbed user agent parsingpull/28788/head^2
parent
1032334b20
commit
bd270b08df
|
@ -34,7 +34,7 @@
|
|||
@import "./components/views/settings/devices/_DeviceExpandDetailsButton.pcss";
|
||||
@import "./components/views/settings/devices/_DeviceSecurityCard.pcss";
|
||||
@import "./components/views/settings/devices/_DeviceTile.pcss";
|
||||
@import "./components/views/settings/devices/_DeviceType.pcss";
|
||||
@import "./components/views/settings/devices/_DeviceTypeIcon.pcss";
|
||||
@import "./components/views/settings/devices/_FilteredDeviceList.pcss";
|
||||
@import "./components/views/settings/devices/_FilteredDeviceListHeader.pcss";
|
||||
@import "./components/views/settings/devices/_SecurityRecommendations.pcss";
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_DeviceType {
|
||||
.mx_DeviceTypeIcon {
|
||||
flex: 0 0 auto;
|
||||
position: relative;
|
||||
margin-right: $spacing-8;
|
||||
|
@ -22,7 +22,7 @@ limitations under the License.
|
|||
padding: 0 $spacing-8 $spacing-8 0;
|
||||
}
|
||||
|
||||
.mx_DeviceType_deviceIcon {
|
||||
.mx_DeviceTypeIcon_deviceIcon {
|
||||
--background-color: $system;
|
||||
--icon-color: $secondary-content;
|
||||
|
||||
|
@ -36,12 +36,12 @@ limitations under the License.
|
|||
background-color: var(--background-color);
|
||||
}
|
||||
|
||||
.mx_DeviceType_selected .mx_DeviceType_deviceIcon {
|
||||
.mx_DeviceTypeIcon_selected .mx_DeviceTypeIcon_deviceIcon {
|
||||
--background-color: $primary-content;
|
||||
--icon-color: $background;
|
||||
}
|
||||
|
||||
.mx_DeviceType_verificationIcon {
|
||||
.mx_DeviceTypeIcon_verificationIcon {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
|
@ -58,7 +58,7 @@ limitations under the License.
|
|||
min-height: 35px;
|
||||
padding: 0 $spacing-8;
|
||||
|
||||
.mx_DeviceType {
|
||||
.mx_DeviceTypeIcon {
|
||||
/* hide the new device type in legacy device list
|
||||
for backwards compat reasons */
|
||||
display: none;
|
||||
|
|
|
@ -29,6 +29,7 @@ import VerificationRequestDialog from '../../views/dialogs/VerificationRequestDi
|
|||
import LogoutDialog from '../dialogs/LogoutDialog';
|
||||
import DeviceTile from './devices/DeviceTile';
|
||||
import SelectableDeviceTile from './devices/SelectableDeviceTile';
|
||||
import { DeviceType } from '../../../utils/device/parseUserAgent';
|
||||
|
||||
interface IProps {
|
||||
device: IMyDevice;
|
||||
|
@ -153,9 +154,10 @@ export default class DevicesPanelEntry extends React.Component<IProps, IState> {
|
|||
</AccessibleButton>
|
||||
</React.Fragment>;
|
||||
|
||||
const deviceWithVerification = {
|
||||
const extendedDevice = {
|
||||
...this.props.device,
|
||||
isVerified: this.props.verified,
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
|
||||
if (this.props.isOwnDevice) {
|
||||
|
@ -163,7 +165,7 @@ export default class DevicesPanelEntry extends React.Component<IProps, IState> {
|
|||
<div className="mx_DevicesPanel_deviceTrust">
|
||||
<span className={"mx_DevicesPanel_icon mx_E2EIcon " + iconClass} />
|
||||
</div>
|
||||
<DeviceTile device={deviceWithVerification}>
|
||||
<DeviceTile device={extendedDevice}>
|
||||
{ buttons }
|
||||
</DeviceTile>
|
||||
</div>;
|
||||
|
@ -171,7 +173,7 @@ export default class DevicesPanelEntry extends React.Component<IProps, IState> {
|
|||
|
||||
return (
|
||||
<div className="mx_DevicesPanel_device">
|
||||
<SelectableDeviceTile device={deviceWithVerification} onClick={this.onDeviceToggled} isSelected={this.props.selected}>
|
||||
<SelectableDeviceTile device={extendedDevice} onClick={this.onDeviceToggled} isSelected={this.props.selected}>
|
||||
{ buttons }
|
||||
</SelectableDeviceTile>
|
||||
</div>
|
||||
|
|
|
@ -24,10 +24,10 @@ import DeviceDetails from './DeviceDetails';
|
|||
import DeviceExpandDetailsButton from './DeviceExpandDetailsButton';
|
||||
import DeviceTile from './DeviceTile';
|
||||
import { DeviceVerificationStatusCard } from './DeviceVerificationStatusCard';
|
||||
import { DeviceWithVerification } from './types';
|
||||
import { ExtendedDevice } from './types';
|
||||
|
||||
interface Props {
|
||||
device?: DeviceWithVerification;
|
||||
device?: ExtendedDevice;
|
||||
isLoading: boolean;
|
||||
isSigningOut: boolean;
|
||||
localNotificationSettings?: LocalNotificationSettings | undefined;
|
||||
|
|
|
@ -22,10 +22,10 @@ import Field from '../../elements/Field';
|
|||
import Spinner from '../../elements/Spinner';
|
||||
import { Caption } from '../../typography/Caption';
|
||||
import Heading from '../../typography/Heading';
|
||||
import { DeviceWithVerification } from './types';
|
||||
import { ExtendedDevice } from './types';
|
||||
|
||||
interface Props {
|
||||
device: DeviceWithVerification;
|
||||
device: ExtendedDevice;
|
||||
saveDeviceName: (deviceName: string) => Promise<void>;
|
||||
}
|
||||
|
||||
|
|
|
@ -72,8 +72,8 @@ const DeviceDetails: React.FC<Props> = ({
|
|||
id: 'application',
|
||||
heading: _t('Application'),
|
||||
values: [
|
||||
{ label: _t('Name'), value: device.clientName },
|
||||
{ label: _t('Version'), value: device.clientVersion },
|
||||
{ label: _t('Name'), value: device.appName },
|
||||
{ label: _t('Version'), value: device.appVersion },
|
||||
{ label: _t('URL'), value: device.url },
|
||||
],
|
||||
},
|
||||
|
|
|
@ -21,16 +21,16 @@ import { _t } from "../../../../languageHandler";
|
|||
import { formatDate, formatRelativeTime } from "../../../../DateUtils";
|
||||
import Heading from "../../typography/Heading";
|
||||
import { INACTIVE_DEVICE_AGE_DAYS, isDeviceInactive } from "./filter";
|
||||
import { DeviceWithVerification } from "./types";
|
||||
import { DeviceType } from "./DeviceType";
|
||||
import { ExtendedDevice } from "./types";
|
||||
import { DeviceTypeIcon } from "./DeviceTypeIcon";
|
||||
export interface DeviceTileProps {
|
||||
device: DeviceWithVerification;
|
||||
device: ExtendedDevice;
|
||||
isSelected?: boolean;
|
||||
children?: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const DeviceTileName: React.FC<{ device: DeviceWithVerification }> = ({ device }) => {
|
||||
const DeviceTileName: React.FC<{ device: ExtendedDevice }> = ({ device }) => {
|
||||
return <Heading size='h4'>
|
||||
{ device.display_name || device.device_id }
|
||||
</Heading>;
|
||||
|
@ -48,7 +48,7 @@ const formatLastActivity = (timestamp: number, now = new Date().getTime()): stri
|
|||
return formatRelativeTime(new Date(timestamp));
|
||||
};
|
||||
|
||||
const getInactiveMetadata = (device: DeviceWithVerification): { id: string, value: React.ReactNode } | undefined => {
|
||||
const getInactiveMetadata = (device: ExtendedDevice): { id: string, value: React.ReactNode } | undefined => {
|
||||
const isInactive = isDeviceInactive(device);
|
||||
|
||||
if (!isInactive) {
|
||||
|
@ -89,7 +89,11 @@ const DeviceTile: React.FC<DeviceTileProps> = ({
|
|||
];
|
||||
|
||||
return <div className="mx_DeviceTile" data-testid={`device-tile-${device.device_id}`}>
|
||||
<DeviceType isVerified={device.isVerified} isSelected={isSelected} />
|
||||
<DeviceTypeIcon
|
||||
isVerified={device.isVerified}
|
||||
isSelected={isSelected}
|
||||
deviceType={device.deviceType}
|
||||
/>
|
||||
<div className="mx_DeviceTile_info" onClick={onClick}>
|
||||
<DeviceTileName device={device} />
|
||||
<div className="mx_DeviceTile_metadata">
|
||||
|
|
|
@ -21,33 +21,39 @@ import { Icon as UnknownDeviceIcon } from '../../../../../res/img/element-icons/
|
|||
import { Icon as VerifiedIcon } from '../../../../../res/img/e2e/verified.svg';
|
||||
import { Icon as UnverifiedIcon } from '../../../../../res/img/e2e/warning.svg';
|
||||
import { _t } from '../../../../languageHandler';
|
||||
import { DeviceWithVerification } from './types';
|
||||
import { ExtendedDevice } from './types';
|
||||
import { DeviceType } from '../../../../utils/device/parseUserAgent';
|
||||
|
||||
interface Props {
|
||||
isVerified?: DeviceWithVerification['isVerified'];
|
||||
isVerified?: ExtendedDevice['isVerified'];
|
||||
isSelected?: boolean;
|
||||
deviceType?: DeviceType;
|
||||
}
|
||||
|
||||
export const DeviceType: React.FC<Props> = ({ isVerified, isSelected }) => (
|
||||
<div className={classNames('mx_DeviceType', {
|
||||
mx_DeviceType_selected: isSelected,
|
||||
export const DeviceTypeIcon: React.FC<Props> = ({
|
||||
isVerified,
|
||||
isSelected,
|
||||
deviceType,
|
||||
}) => (
|
||||
<div className={classNames('mx_DeviceTypeIcon', {
|
||||
mx_DeviceTypeIcon_selected: isSelected,
|
||||
})}
|
||||
>
|
||||
{ /* TODO(kerrya) all devices have an unknown type until PSG-650 */ }
|
||||
<UnknownDeviceIcon
|
||||
className='mx_DeviceType_deviceIcon'
|
||||
className='mx_DeviceTypeIcon_deviceIcon'
|
||||
role='img'
|
||||
aria-label={_t('Unknown device type')}
|
||||
/>
|
||||
{
|
||||
isVerified
|
||||
? <VerifiedIcon
|
||||
className={classNames('mx_DeviceType_verificationIcon', 'verified')}
|
||||
className={classNames('mx_DeviceTypeIcon_verificationIcon', 'verified')}
|
||||
role='img'
|
||||
aria-label={_t('Verified')}
|
||||
/>
|
||||
: <UnverifiedIcon
|
||||
className={classNames('mx_DeviceType_verificationIcon', 'unverified')}
|
||||
className={classNames('mx_DeviceTypeIcon_verificationIcon', 'unverified')}
|
||||
role='img'
|
||||
aria-label={_t('Unverified')}
|
||||
/>
|
|
@ -21,11 +21,11 @@ import AccessibleButton from '../../elements/AccessibleButton';
|
|||
import DeviceSecurityCard from './DeviceSecurityCard';
|
||||
import {
|
||||
DeviceSecurityVariation,
|
||||
DeviceWithVerification,
|
||||
ExtendedDevice,
|
||||
} from './types';
|
||||
|
||||
interface Props {
|
||||
device: DeviceWithVerification;
|
||||
device: ExtendedDevice;
|
||||
onVerifyDevice?: () => void;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ import SelectableDeviceTile from './SelectableDeviceTile';
|
|||
import {
|
||||
DevicesDictionary,
|
||||
DeviceSecurityVariation,
|
||||
DeviceWithVerification,
|
||||
ExtendedDevice,
|
||||
} from './types';
|
||||
import { DevicesState } from './useOwnDevices';
|
||||
import FilteredDeviceListHeader from './FilteredDeviceListHeader';
|
||||
|
@ -42,27 +42,27 @@ interface Props {
|
|||
devices: DevicesDictionary;
|
||||
pushers: IPusher[];
|
||||
localNotificationSettings: Map<string, LocalNotificationSettings>;
|
||||
expandedDeviceIds: DeviceWithVerification['device_id'][];
|
||||
signingOutDeviceIds: DeviceWithVerification['device_id'][];
|
||||
selectedDeviceIds: DeviceWithVerification['device_id'][];
|
||||
expandedDeviceIds: ExtendedDevice['device_id'][];
|
||||
signingOutDeviceIds: ExtendedDevice['device_id'][];
|
||||
selectedDeviceIds: ExtendedDevice['device_id'][];
|
||||
filter?: DeviceSecurityVariation;
|
||||
onFilterChange: (filter: DeviceSecurityVariation | undefined) => void;
|
||||
onDeviceExpandToggle: (deviceId: DeviceWithVerification['device_id']) => void;
|
||||
onSignOutDevices: (deviceIds: DeviceWithVerification['device_id'][]) => void;
|
||||
onDeviceExpandToggle: (deviceId: ExtendedDevice['device_id']) => void;
|
||||
onSignOutDevices: (deviceIds: ExtendedDevice['device_id'][]) => void;
|
||||
saveDeviceName: DevicesState['saveDeviceName'];
|
||||
onRequestDeviceVerification?: (deviceId: DeviceWithVerification['device_id']) => void;
|
||||
onRequestDeviceVerification?: (deviceId: ExtendedDevice['device_id']) => void;
|
||||
setPushNotifications: (deviceId: string, enabled: boolean) => Promise<void>;
|
||||
setSelectedDeviceIds: (deviceIds: DeviceWithVerification['device_id'][]) => void;
|
||||
setSelectedDeviceIds: (deviceIds: ExtendedDevice['device_id'][]) => void;
|
||||
supportsMSC3881?: boolean | undefined;
|
||||
}
|
||||
|
||||
const isDeviceSelected = (
|
||||
deviceId: DeviceWithVerification['device_id'],
|
||||
selectedDeviceIds: DeviceWithVerification['device_id'][],
|
||||
deviceId: ExtendedDevice['device_id'],
|
||||
selectedDeviceIds: ExtendedDevice['device_id'][],
|
||||
) => selectedDeviceIds.includes(deviceId);
|
||||
|
||||
// devices without timestamp metadata should be sorted last
|
||||
const sortDevicesByLatestActivity = (left: DeviceWithVerification, right: DeviceWithVerification) =>
|
||||
const sortDevicesByLatestActivity = (left: ExtendedDevice, right: ExtendedDevice) =>
|
||||
(right.last_seen_ts || 0) - (left.last_seen_ts || 0);
|
||||
|
||||
const getFilteredSortedDevices = (devices: DevicesDictionary, filter?: DeviceSecurityVariation) =>
|
||||
|
@ -149,7 +149,7 @@ const NoResults: React.FC<NoResultsProps> = ({ filter, clearFilter }) =>
|
|||
</div>;
|
||||
|
||||
const DeviceListItem: React.FC<{
|
||||
device: DeviceWithVerification;
|
||||
device: ExtendedDevice;
|
||||
pusher?: IPusher | undefined;
|
||||
localNotificationSettings?: LocalNotificationSettings | undefined;
|
||||
isExpanded: boolean;
|
||||
|
@ -227,11 +227,11 @@ export const FilteredDeviceList =
|
|||
}: Props, ref: ForwardedRef<HTMLDivElement>) => {
|
||||
const sortedDevices = getFilteredSortedDevices(devices, filter);
|
||||
|
||||
function getPusherForDevice(device: DeviceWithVerification): IPusher | undefined {
|
||||
function getPusherForDevice(device: ExtendedDevice): IPusher | undefined {
|
||||
return pushers.find(pusher => pusher[PUSHER_DEVICE_ID.name] === device.device_id);
|
||||
}
|
||||
|
||||
const toggleSelection = (deviceId: DeviceWithVerification['device_id']): void => {
|
||||
const toggleSelection = (deviceId: ExtendedDevice['device_id']): void => {
|
||||
if (isDeviceSelected(deviceId, selectedDeviceIds)) {
|
||||
// remove from selection
|
||||
setSelectedDeviceIds(selectedDeviceIds.filter(id => id !== deviceId));
|
||||
|
|
|
@ -23,13 +23,13 @@ import DeviceSecurityCard from './DeviceSecurityCard';
|
|||
import { filterDevicesBySecurityRecommendation, INACTIVE_DEVICE_AGE_DAYS } from './filter';
|
||||
import {
|
||||
DeviceSecurityVariation,
|
||||
DeviceWithVerification,
|
||||
ExtendedDevice,
|
||||
DevicesDictionary,
|
||||
} from './types';
|
||||
|
||||
interface Props {
|
||||
devices: DevicesDictionary;
|
||||
currentDeviceId: DeviceWithVerification['device_id'];
|
||||
currentDeviceId: ExtendedDevice['device_id'];
|
||||
goToFilteredList: (filter: DeviceSecurityVariation) => void;
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ const SecurityRecommendations: React.FC<Props> = ({
|
|||
currentDeviceId,
|
||||
goToFilteredList,
|
||||
}) => {
|
||||
const devicesArray = Object.values<DeviceWithVerification>(devices);
|
||||
const devicesArray = Object.values<ExtendedDevice>(devices);
|
||||
|
||||
const unverifiedDevicesCount = filterDevicesBySecurityRecommendation(
|
||||
devicesArray,
|
||||
|
|
|
@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { DeviceWithVerification, DeviceSecurityVariation } from "./types";
|
||||
import { ExtendedDevice, DeviceSecurityVariation } from "./types";
|
||||
|
||||
type DeviceFilterCondition = (device: DeviceWithVerification) => boolean;
|
||||
type DeviceFilterCondition = (device: ExtendedDevice) => boolean;
|
||||
|
||||
const MS_DAY = 24 * 60 * 60 * 1000;
|
||||
export const INACTIVE_DEVICE_AGE_MS = 7.776e+9; // 90 days
|
||||
|
@ -32,7 +32,7 @@ const filters: Record<DeviceSecurityVariation, DeviceFilterCondition> = {
|
|||
};
|
||||
|
||||
export const filterDevicesBySecurityRecommendation = (
|
||||
devices: DeviceWithVerification[],
|
||||
devices: ExtendedDevice[],
|
||||
securityVariations: DeviceSecurityVariation[],
|
||||
) => {
|
||||
const activeFilters = securityVariations.map(variation => filters[variation]);
|
||||
|
|
|
@ -16,14 +16,17 @@ limitations under the License.
|
|||
|
||||
import { IMyDevice } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { ExtendedDeviceInformation } from "../../../../utils/device/parseUserAgent";
|
||||
|
||||
export type DeviceWithVerification = IMyDevice & { isVerified: boolean | null };
|
||||
export type ExtendedDeviceInfo = {
|
||||
clientName?: string;
|
||||
clientVersion?: string;
|
||||
export type ExtendedDeviceAppInfo = {
|
||||
// eg Element Web
|
||||
appName?: string;
|
||||
appVersion?: string;
|
||||
url?: string;
|
||||
};
|
||||
export type ExtendedDevice = DeviceWithVerification & ExtendedDeviceInfo;
|
||||
export type DevicesDictionary = Record<DeviceWithVerification['device_id'], ExtendedDevice>;
|
||||
export type ExtendedDevice = DeviceWithVerification & ExtendedDeviceAppInfo & ExtendedDeviceInformation;
|
||||
export type DevicesDictionary = Record<ExtendedDevice['device_id'], ExtendedDevice>;
|
||||
|
||||
export enum DeviceSecurityVariation {
|
||||
Verified = 'Verified',
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
MatrixEvent,
|
||||
PUSHER_DEVICE_ID,
|
||||
PUSHER_ENABLED,
|
||||
UNSTABLE_MSC3852_LAST_SEEN_UA,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { CrossSigningInfo } from "matrix-js-sdk/src/crypto/CrossSigning";
|
||||
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||
|
@ -34,8 +35,9 @@ import { LocalNotificationSettings } from "matrix-js-sdk/src/@types/local_notifi
|
|||
import MatrixClientContext from "../../../../contexts/MatrixClientContext";
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import { getDeviceClientInformation } from "../../../../utils/device/clientInformation";
|
||||
import { DevicesDictionary, DeviceWithVerification, ExtendedDeviceInfo } from "./types";
|
||||
import { DevicesDictionary, ExtendedDevice, ExtendedDeviceAppInfo } from "./types";
|
||||
import { useEventEmitter } from "../../../../hooks/useEventEmitter";
|
||||
import { parseUserAgent } from "../../../../utils/device/parseUserAgent";
|
||||
|
||||
const isDeviceVerified = (
|
||||
matrixClient: MatrixClient,
|
||||
|
@ -63,12 +65,12 @@ const isDeviceVerified = (
|
|||
}
|
||||
};
|
||||
|
||||
const parseDeviceExtendedInformation = (matrixClient: MatrixClient, device: IMyDevice): ExtendedDeviceInfo => {
|
||||
const parseDeviceExtendedInformation = (matrixClient: MatrixClient, device: IMyDevice): ExtendedDeviceAppInfo => {
|
||||
const { name, version, url } = getDeviceClientInformation(matrixClient, device.device_id);
|
||||
|
||||
return {
|
||||
clientName: name,
|
||||
clientVersion: version,
|
||||
appName: name,
|
||||
appVersion: version,
|
||||
url,
|
||||
};
|
||||
};
|
||||
|
@ -87,6 +89,7 @@ const fetchDevicesWithVerification = async (
|
|||
...device,
|
||||
isVerified: isDeviceVerified(matrixClient, crossSigningInfo, device),
|
||||
...parseDeviceExtendedInformation(matrixClient, device),
|
||||
...parseUserAgent(device[UNSTABLE_MSC3852_LAST_SEEN_UA.name]),
|
||||
},
|
||||
}), {});
|
||||
|
||||
|
@ -104,10 +107,10 @@ export type DevicesState = {
|
|||
currentDeviceId: string;
|
||||
isLoadingDeviceList: boolean;
|
||||
// not provided when current session cannot request verification
|
||||
requestDeviceVerification?: (deviceId: DeviceWithVerification['device_id']) => Promise<VerificationRequest>;
|
||||
requestDeviceVerification?: (deviceId: ExtendedDevice['device_id']) => Promise<VerificationRequest>;
|
||||
refreshDevices: () => Promise<void>;
|
||||
saveDeviceName: (deviceId: DeviceWithVerification['device_id'], deviceName: string) => Promise<void>;
|
||||
setPushNotifications: (deviceId: DeviceWithVerification['device_id'], enabled: boolean) => Promise<void>;
|
||||
saveDeviceName: (deviceId: ExtendedDevice['device_id'], deviceName: string) => Promise<void>;
|
||||
setPushNotifications: (deviceId: ExtendedDevice['device_id'], enabled: boolean) => Promise<void>;
|
||||
error?: OwnDevicesError;
|
||||
supportsMSC3881?: boolean | undefined;
|
||||
};
|
||||
|
@ -189,7 +192,7 @@ export const useOwnDevices = (): DevicesState => {
|
|||
const isCurrentDeviceVerified = !!devices[currentDeviceId]?.isVerified;
|
||||
|
||||
const requestDeviceVerification = isCurrentDeviceVerified && userId
|
||||
? async (deviceId: DeviceWithVerification['device_id']) => {
|
||||
? async (deviceId: ExtendedDevice['device_id']) => {
|
||||
return await matrixClient.requestVerification(
|
||||
userId,
|
||||
[deviceId],
|
||||
|
@ -198,7 +201,7 @@ export const useOwnDevices = (): DevicesState => {
|
|||
: undefined;
|
||||
|
||||
const saveDeviceName = useCallback(
|
||||
async (deviceId: DeviceWithVerification['device_id'], deviceName: string): Promise<void> => {
|
||||
async (deviceId: ExtendedDevice['device_id'], deviceName: string): Promise<void> => {
|
||||
const device = devices[deviceId];
|
||||
|
||||
// no change
|
||||
|
@ -219,7 +222,7 @@ export const useOwnDevices = (): DevicesState => {
|
|||
}, [matrixClient, devices, refreshDevices]);
|
||||
|
||||
const setPushNotifications = useCallback(
|
||||
async (deviceId: DeviceWithVerification['device_id'], enabled: boolean): Promise<void> => {
|
||||
async (deviceId: ExtendedDevice['device_id'], enabled: boolean): Promise<void> => {
|
||||
try {
|
||||
const pusher = pushers.find(pusher => pusher[PUSHER_DEVICE_ID.name] === deviceId);
|
||||
if (pusher) {
|
||||
|
|
|
@ -29,7 +29,7 @@ import { useOwnDevices } from '../../devices/useOwnDevices';
|
|||
import { FilteredDeviceList } from '../../devices/FilteredDeviceList';
|
||||
import CurrentDeviceSection from '../../devices/CurrentDeviceSection';
|
||||
import SecurityRecommendations from '../../devices/SecurityRecommendations';
|
||||
import { DeviceSecurityVariation, DeviceWithVerification } from '../../devices/types';
|
||||
import { DeviceSecurityVariation, ExtendedDevice } from '../../devices/types';
|
||||
import { deleteDevicesWithInteractiveAuth } from '../../devices/deleteDevices';
|
||||
import SettingsTab from '../SettingsTab';
|
||||
|
||||
|
@ -38,10 +38,10 @@ const useSignOut = (
|
|||
onSignoutResolvedCallback: () => Promise<void>,
|
||||
): {
|
||||
onSignOutCurrentDevice: () => void;
|
||||
onSignOutOtherDevices: (deviceIds: DeviceWithVerification['device_id'][]) => Promise<void>;
|
||||
signingOutDeviceIds: DeviceWithVerification['device_id'][];
|
||||
onSignOutOtherDevices: (deviceIds: ExtendedDevice['device_id'][]) => Promise<void>;
|
||||
signingOutDeviceIds: ExtendedDevice['device_id'][];
|
||||
} => {
|
||||
const [signingOutDeviceIds, setSigningOutDeviceIds] = useState<DeviceWithVerification['device_id'][]>([]);
|
||||
const [signingOutDeviceIds, setSigningOutDeviceIds] = useState<ExtendedDevice['device_id'][]>([]);
|
||||
|
||||
const onSignOutCurrentDevice = () => {
|
||||
Modal.createDialog(
|
||||
|
@ -53,7 +53,7 @@ const useSignOut = (
|
|||
);
|
||||
};
|
||||
|
||||
const onSignOutOtherDevices = async (deviceIds: DeviceWithVerification['device_id'][]) => {
|
||||
const onSignOutOtherDevices = async (deviceIds: ExtendedDevice['device_id'][]) => {
|
||||
if (!deviceIds.length) {
|
||||
return;
|
||||
}
|
||||
|
@ -96,8 +96,8 @@ const SessionManagerTab: React.FC = () => {
|
|||
supportsMSC3881,
|
||||
} = useOwnDevices();
|
||||
const [filter, setFilter] = useState<DeviceSecurityVariation>();
|
||||
const [expandedDeviceIds, setExpandedDeviceIds] = useState<DeviceWithVerification['device_id'][]>([]);
|
||||
const [selectedDeviceIds, setSelectedDeviceIds] = useState<DeviceWithVerification['device_id'][]>([]);
|
||||
const [expandedDeviceIds, setExpandedDeviceIds] = useState<ExtendedDevice['device_id'][]>([]);
|
||||
const [selectedDeviceIds, setSelectedDeviceIds] = useState<ExtendedDevice['device_id'][]>([]);
|
||||
const filteredDeviceListRef = useRef<HTMLDivElement>(null);
|
||||
const scrollIntoViewTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
||||
|
||||
|
@ -105,7 +105,7 @@ const SessionManagerTab: React.FC = () => {
|
|||
const userId = matrixClient.getUserId();
|
||||
const currentUserMember = userId && matrixClient.getUser(userId) || undefined;
|
||||
|
||||
const onDeviceExpandToggle = (deviceId: DeviceWithVerification['device_id']): void => {
|
||||
const onDeviceExpandToggle = (deviceId: ExtendedDevice['device_id']): void => {
|
||||
if (expandedDeviceIds.includes(deviceId)) {
|
||||
setExpandedDeviceIds(expandedDeviceIds.filter(id => id !== deviceId));
|
||||
} else {
|
||||
|
@ -136,7 +136,7 @@ const SessionManagerTab: React.FC = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const onTriggerDeviceVerification = useCallback((deviceId: DeviceWithVerification['device_id']) => {
|
||||
const onTriggerDeviceVerification = useCallback((deviceId: ExtendedDevice['device_id']) => {
|
||||
if (!requestDeviceVerification) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export enum DeviceType {
|
||||
Desktop = 'Desktop',
|
||||
Mobile = 'Mobile',
|
||||
Web = 'Web',
|
||||
Unknown = 'Unknown',
|
||||
}
|
||||
export type ExtendedDeviceInformation = {
|
||||
deviceType: DeviceType;
|
||||
// eg Google Pixel 6
|
||||
deviceModel?: string;
|
||||
// eg Android 11
|
||||
deviceOperatingSystem?: string;
|
||||
// eg Firefox
|
||||
clientName?: string;
|
||||
// eg 1.1.0
|
||||
clientVersion?: string;
|
||||
};
|
||||
|
||||
export const parseUserAgent = (userAgent?: string): ExtendedDeviceInformation => {
|
||||
if (!userAgent) {
|
||||
return {
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
}
|
||||
// @TODO(kerrya) not yet implemented
|
||||
return {
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
};
|
|
@ -112,16 +112,16 @@ exports[`<DevicesPanel /> renders device panel with devices 1`] = `
|
|||
data-testid="device-tile-device_1"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
@ -235,16 +235,16 @@ exports[`<DevicesPanel /> renders device panel with devices 1`] = `
|
|||
data-testid="device-tile-device_2"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
@ -317,16 +317,16 @@ exports[`<DevicesPanel /> renders device panel with devices 1`] = `
|
|||
data-testid="device-tile-device_3"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -19,6 +19,7 @@ import { fireEvent, render } from '@testing-library/react';
|
|||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import CurrentDeviceSection from '../../../../../src/components/views/settings/devices/CurrentDeviceSection';
|
||||
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
|
||||
|
||||
describe('<CurrentDeviceSection />', () => {
|
||||
const deviceId = 'alices_device';
|
||||
|
@ -26,10 +27,12 @@ describe('<CurrentDeviceSection />', () => {
|
|||
const alicesVerifiedDevice = {
|
||||
device_id: deviceId,
|
||||
isVerified: false,
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const alicesUnverifiedDevice = {
|
||||
device_id: deviceId,
|
||||
isVerified: false,
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
|
|
|
@ -19,6 +19,7 @@ import { fireEvent, render, RenderResult } from '@testing-library/react';
|
|||
|
||||
import { DeviceDetailHeading } from '../../../../../src/components/views/settings/devices/DeviceDetailHeading';
|
||||
import { flushPromisesWithFakeTimers } from '../../../../test-utils';
|
||||
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
|
@ -27,6 +28,7 @@ describe('<DeviceDetailHeading />', () => {
|
|||
device_id: '123',
|
||||
display_name: 'My device',
|
||||
isVerified: true,
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const defaultProps = {
|
||||
device,
|
||||
|
|
|
@ -20,11 +20,13 @@ import { PUSHER_ENABLED } from 'matrix-js-sdk/src/@types/event';
|
|||
|
||||
import DeviceDetails from '../../../../../src/components/views/settings/devices/DeviceDetails';
|
||||
import { mkPusher } from '../../../../test-utils/test-utils';
|
||||
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
|
||||
|
||||
describe('<DeviceDetails />', () => {
|
||||
const baseDevice = {
|
||||
device_id: 'my-device',
|
||||
isVerified: false,
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const defaultProps = {
|
||||
device: baseDevice,
|
||||
|
@ -58,7 +60,7 @@ describe('<DeviceDetails />', () => {
|
|||
display_name: 'My Device',
|
||||
last_seen_ip: '123.456.789',
|
||||
last_seen_ts: now - 60000000,
|
||||
clientName: 'Element Web',
|
||||
appName: 'Element Web',
|
||||
};
|
||||
const { container } = render(getComponent({ device }));
|
||||
expect(container).toMatchSnapshot();
|
||||
|
|
|
@ -19,12 +19,14 @@ import { render } from '@testing-library/react';
|
|||
import { IMyDevice } from 'matrix-js-sdk/src/matrix';
|
||||
|
||||
import DeviceTile from '../../../../../src/components/views/settings/devices/DeviceTile';
|
||||
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
|
||||
|
||||
describe('<DeviceTile />', () => {
|
||||
const defaultProps = {
|
||||
device: {
|
||||
device_id: '123',
|
||||
isVerified: false,
|
||||
deviceType: DeviceType.Unknown,
|
||||
},
|
||||
};
|
||||
const getComponent = (props = {}) => (
|
||||
|
|
|
@ -17,15 +17,15 @@ limitations under the License.
|
|||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { DeviceType } from '../../../../../src/components/views/settings/devices/DeviceType';
|
||||
import { DeviceTypeIcon } from '../../../../../src/components/views/settings/devices/DeviceTypeIcon';
|
||||
|
||||
describe('<DeviceType />', () => {
|
||||
describe('<DeviceTypeIcon />', () => {
|
||||
const defaultProps = {
|
||||
isVerified: false,
|
||||
isSelected: false,
|
||||
};
|
||||
const getComponent = (props = {}) =>
|
||||
<DeviceType {...defaultProps} {...props} />;
|
||||
<DeviceTypeIcon {...defaultProps} {...props} />;
|
||||
|
||||
it('renders an unverified device', () => {
|
||||
const { container } = render(getComponent());
|
|
@ -20,6 +20,7 @@ import { act, fireEvent, render } from '@testing-library/react';
|
|||
import { FilteredDeviceList } from '../../../../../src/components/views/settings/devices/FilteredDeviceList';
|
||||
import { DeviceSecurityVariation } from '../../../../../src/components/views/settings/devices/types';
|
||||
import { flushPromises, mockPlatformPeg } from '../../../../test-utils';
|
||||
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
|
||||
|
||||
mockPlatformPeg();
|
||||
|
||||
|
@ -31,14 +32,26 @@ describe('<FilteredDeviceList />', () => {
|
|||
last_seen_ip: '123.456.789',
|
||||
display_name: 'My Device',
|
||||
isVerified: true,
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const unverifiedNoMetadata = { device_id: 'unverified-no-metadata', isVerified: false };
|
||||
const verifiedNoMetadata = { device_id: 'verified-no-metadata', isVerified: true };
|
||||
const hundredDaysOld = { device_id: '100-days-old', isVerified: true, last_seen_ts: Date.now() - (MS_DAY * 100) };
|
||||
const unverifiedNoMetadata = {
|
||||
device_id: 'unverified-no-metadata',
|
||||
isVerified: false,
|
||||
deviceType: DeviceType.Unknown };
|
||||
const verifiedNoMetadata = {
|
||||
device_id: 'verified-no-metadata',
|
||||
isVerified: true,
|
||||
deviceType: DeviceType.Unknown };
|
||||
const hundredDaysOld = {
|
||||
device_id: '100-days-old',
|
||||
isVerified: true,
|
||||
last_seen_ts: Date.now() - (MS_DAY * 100),
|
||||
deviceType: DeviceType.Unknown };
|
||||
const hundredDaysOldUnverified = {
|
||||
device_id: 'unverified-100-days-old',
|
||||
isVerified: false,
|
||||
last_seen_ts: Date.now() - (MS_DAY * 100),
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const defaultProps = {
|
||||
onFilterChange: jest.fn(),
|
||||
|
|
|
@ -19,6 +19,7 @@ import React from 'react';
|
|||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import SelectableDeviceTile from '../../../../../src/components/views/settings/devices/SelectableDeviceTile';
|
||||
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
|
||||
|
||||
describe('<SelectableDeviceTile />', () => {
|
||||
const device = {
|
||||
|
@ -26,6 +27,7 @@ describe('<SelectableDeviceTile />', () => {
|
|||
device_id: 'my-device',
|
||||
last_seen_ip: '123.456.789',
|
||||
isVerified: false,
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const defaultProps = {
|
||||
onClick: jest.fn(),
|
||||
|
|
|
@ -151,16 +151,16 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
|
|||
data-testid="device-tile-alices_device"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
@ -267,16 +267,16 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
|
|||
data-testid="device-tile-alices_device"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -7,16 +7,16 @@ exports[`<DeviceTile /> renders a device with no metadata 1`] = `
|
|||
data-testid="device-tile-123"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
@ -58,16 +58,16 @@ exports[`<DeviceTile /> renders a verified device with no metadata 1`] = `
|
|||
data-testid="device-tile-123"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
@ -109,16 +109,16 @@ exports[`<DeviceTile /> renders display name with a tooltip 1`] = `
|
|||
data-testid="device-tile-123"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
@ -160,16 +160,16 @@ exports[`<DeviceTile /> separates metadata with a dot 1`] = `
|
|||
data-testid="device-tile-123"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<DeviceType /> renders a verified device 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Verified"
|
||||
class="mx_DeviceType_verificationIcon verified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<DeviceType /> renders an unverified device 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<DeviceType /> renders correctly when selected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_DeviceType mx_DeviceType_selected"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,58 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<DeviceTypeIcon /> renders a verified device 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Verified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon verified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<DeviceTypeIcon /> renders an unverified device 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<DeviceTypeIcon /> renders correctly when selected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_DeviceTypeIcon mx_DeviceTypeIcon_selected"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -39,16 +39,16 @@ exports[`<SelectableDeviceTile /> renders unselected device tile with checkbox 1
|
|||
data-testid="device-tile-my-device"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -20,18 +20,38 @@ import {
|
|||
import {
|
||||
DeviceSecurityVariation,
|
||||
} from "../../../../../src/components/views/settings/devices/types";
|
||||
import { DeviceType } from "../../../../../src/utils/device/parseUserAgent";
|
||||
|
||||
const MS_DAY = 86400000;
|
||||
describe('filterDevicesBySecurityRecommendation()', () => {
|
||||
const unverifiedNoMetadata = { device_id: 'unverified-no-metadata', isVerified: false };
|
||||
const verifiedNoMetadata = { device_id: 'verified-no-metadata', isVerified: true };
|
||||
const hundredDaysOld = { device_id: '100-days-old', isVerified: true, last_seen_ts: Date.now() - (MS_DAY * 100) };
|
||||
const unverifiedNoMetadata = {
|
||||
device_id: 'unverified-no-metadata',
|
||||
isVerified: false,
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const verifiedNoMetadata = {
|
||||
device_id: 'verified-no-metadata',
|
||||
isVerified: true,
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const hundredDaysOld = {
|
||||
device_id: '100-days-old',
|
||||
isVerified: true,
|
||||
last_seen_ts: Date.now() - (MS_DAY * 100),
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const hundredDaysOldUnverified = {
|
||||
device_id: 'unverified-100-days-old',
|
||||
isVerified: false,
|
||||
last_seen_ts: Date.now() - (MS_DAY * 100),
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const fiftyDaysOld = {
|
||||
device_id: '50-days-old',
|
||||
isVerified: true,
|
||||
last_seen_ts: Date.now() - (MS_DAY * 50),
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const fiftyDaysOld = { device_id: '50-days-old', isVerified: true, last_seen_ts: Date.now() - (MS_DAY * 50) };
|
||||
|
||||
const devices = [
|
||||
unverifiedNoMetadata,
|
||||
|
|
|
@ -44,7 +44,7 @@ import Modal from '../../../../../../src/Modal';
|
|||
import LogoutDialog from '../../../../../../src/components/views/dialogs/LogoutDialog';
|
||||
import {
|
||||
DeviceSecurityVariation,
|
||||
DeviceWithVerification,
|
||||
ExtendedDevice,
|
||||
} from '../../../../../../src/components/views/settings/devices/types';
|
||||
import { INACTIVE_DEVICE_AGE_MS } from '../../../../../../src/components/views/settings/devices/filter';
|
||||
|
||||
|
@ -104,7 +104,7 @@ describe('<SessionManagerTab />', () => {
|
|||
|
||||
const toggleDeviceDetails = (
|
||||
getByTestId: ReturnType<typeof render>['getByTestId'],
|
||||
deviceId: DeviceWithVerification['device_id'],
|
||||
deviceId: ExtendedDevice['device_id'],
|
||||
) => {
|
||||
// open device detail
|
||||
const tile = getByTestId(`device-tile-${deviceId}`);
|
||||
|
@ -114,7 +114,7 @@ describe('<SessionManagerTab />', () => {
|
|||
|
||||
const toggleDeviceSelection = (
|
||||
getByTestId: ReturnType<typeof render>['getByTestId'],
|
||||
deviceId: DeviceWithVerification['device_id'],
|
||||
deviceId: ExtendedDevice['device_id'],
|
||||
) => {
|
||||
const checkbox = getByTestId(`device-tile-checkbox-${deviceId}`);
|
||||
fireEvent.click(checkbox);
|
||||
|
@ -135,7 +135,7 @@ describe('<SessionManagerTab />', () => {
|
|||
|
||||
const isDeviceSelected = (
|
||||
getByTestId: ReturnType<typeof render>['getByTestId'],
|
||||
deviceId: DeviceWithVerification['device_id'],
|
||||
deviceId: ExtendedDevice['device_id'],
|
||||
): boolean => !!(getByTestId(`device-tile-checkbox-${deviceId}`) as HTMLInputElement).checked;
|
||||
|
||||
const isSelectAllChecked = (
|
||||
|
|
|
@ -94,16 +94,16 @@ exports[`<SessionManagerTab /> renders current session section with a verified s
|
|||
data-testid="device-tile-alices_device"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Verified"
|
||||
class="mx_DeviceType_verificationIcon verified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon verified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
@ -196,16 +196,16 @@ exports[`<SessionManagerTab /> renders current session section with an unverifie
|
|||
data-testid="device-tile-alices_device"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
@ -298,16 +298,16 @@ exports[`<SessionManagerTab /> sets device verification status correctly 1`] = `
|
|||
data-testid="device-tile-alices_device"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Verified"
|
||||
class="mx_DeviceType_verificationIcon verified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon verified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { DeviceType, parseUserAgent } from "../../../src/utils/device/parseUserAgent";
|
||||
|
||||
describe('parseUserAgent()', () => {
|
||||
it('returns deviceType unknown when user agent is falsy', () => {
|
||||
expect(parseUserAgent(undefined)).toEqual({
|
||||
deviceType: DeviceType.Unknown,
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue