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/_DeviceExpandDetailsButton.pcss";
|
||||||
@import "./components/views/settings/devices/_DeviceSecurityCard.pcss";
|
@import "./components/views/settings/devices/_DeviceSecurityCard.pcss";
|
||||||
@import "./components/views/settings/devices/_DeviceTile.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/_FilteredDeviceList.pcss";
|
||||||
@import "./components/views/settings/devices/_FilteredDeviceListHeader.pcss";
|
@import "./components/views/settings/devices/_FilteredDeviceListHeader.pcss";
|
||||||
@import "./components/views/settings/devices/_SecurityRecommendations.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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_DeviceType {
|
.mx_DeviceTypeIcon {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-right: $spacing-8;
|
margin-right: $spacing-8;
|
||||||
|
@ -22,7 +22,7 @@ limitations under the License.
|
||||||
padding: 0 $spacing-8 $spacing-8 0;
|
padding: 0 $spacing-8 $spacing-8 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DeviceType_deviceIcon {
|
.mx_DeviceTypeIcon_deviceIcon {
|
||||||
--background-color: $system;
|
--background-color: $system;
|
||||||
--icon-color: $secondary-content;
|
--icon-color: $secondary-content;
|
||||||
|
|
||||||
|
@ -36,12 +36,12 @@ limitations under the License.
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DeviceType_selected .mx_DeviceType_deviceIcon {
|
.mx_DeviceTypeIcon_selected .mx_DeviceTypeIcon_deviceIcon {
|
||||||
--background-color: $primary-content;
|
--background-color: $primary-content;
|
||||||
--icon-color: $background;
|
--icon-color: $background;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DeviceType_verificationIcon {
|
.mx_DeviceTypeIcon_verificationIcon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
|
@ -58,7 +58,7 @@ limitations under the License.
|
||||||
min-height: 35px;
|
min-height: 35px;
|
||||||
padding: 0 $spacing-8;
|
padding: 0 $spacing-8;
|
||||||
|
|
||||||
.mx_DeviceType {
|
.mx_DeviceTypeIcon {
|
||||||
/* hide the new device type in legacy device list
|
/* hide the new device type in legacy device list
|
||||||
for backwards compat reasons */
|
for backwards compat reasons */
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
@ -29,6 +29,7 @@ import VerificationRequestDialog from '../../views/dialogs/VerificationRequestDi
|
||||||
import LogoutDialog from '../dialogs/LogoutDialog';
|
import LogoutDialog from '../dialogs/LogoutDialog';
|
||||||
import DeviceTile from './devices/DeviceTile';
|
import DeviceTile from './devices/DeviceTile';
|
||||||
import SelectableDeviceTile from './devices/SelectableDeviceTile';
|
import SelectableDeviceTile from './devices/SelectableDeviceTile';
|
||||||
|
import { DeviceType } from '../../../utils/device/parseUserAgent';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
device: IMyDevice;
|
device: IMyDevice;
|
||||||
|
@ -153,9 +154,10 @@ export default class DevicesPanelEntry extends React.Component<IProps, IState> {
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
|
|
||||||
const deviceWithVerification = {
|
const extendedDevice = {
|
||||||
...this.props.device,
|
...this.props.device,
|
||||||
isVerified: this.props.verified,
|
isVerified: this.props.verified,
|
||||||
|
deviceType: DeviceType.Unknown,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.props.isOwnDevice) {
|
if (this.props.isOwnDevice) {
|
||||||
|
@ -163,7 +165,7 @@ export default class DevicesPanelEntry extends React.Component<IProps, IState> {
|
||||||
<div className="mx_DevicesPanel_deviceTrust">
|
<div className="mx_DevicesPanel_deviceTrust">
|
||||||
<span className={"mx_DevicesPanel_icon mx_E2EIcon " + iconClass} />
|
<span className={"mx_DevicesPanel_icon mx_E2EIcon " + iconClass} />
|
||||||
</div>
|
</div>
|
||||||
<DeviceTile device={deviceWithVerification}>
|
<DeviceTile device={extendedDevice}>
|
||||||
{ buttons }
|
{ buttons }
|
||||||
</DeviceTile>
|
</DeviceTile>
|
||||||
</div>;
|
</div>;
|
||||||
|
@ -171,7 +173,7 @@ export default class DevicesPanelEntry extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_DevicesPanel_device">
|
<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 }
|
{ buttons }
|
||||||
</SelectableDeviceTile>
|
</SelectableDeviceTile>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -24,10 +24,10 @@ import DeviceDetails from './DeviceDetails';
|
||||||
import DeviceExpandDetailsButton from './DeviceExpandDetailsButton';
|
import DeviceExpandDetailsButton from './DeviceExpandDetailsButton';
|
||||||
import DeviceTile from './DeviceTile';
|
import DeviceTile from './DeviceTile';
|
||||||
import { DeviceVerificationStatusCard } from './DeviceVerificationStatusCard';
|
import { DeviceVerificationStatusCard } from './DeviceVerificationStatusCard';
|
||||||
import { DeviceWithVerification } from './types';
|
import { ExtendedDevice } from './types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
device?: DeviceWithVerification;
|
device?: ExtendedDevice;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
isSigningOut: boolean;
|
isSigningOut: boolean;
|
||||||
localNotificationSettings?: LocalNotificationSettings | undefined;
|
localNotificationSettings?: LocalNotificationSettings | undefined;
|
||||||
|
|
|
@ -22,10 +22,10 @@ import Field from '../../elements/Field';
|
||||||
import Spinner from '../../elements/Spinner';
|
import Spinner from '../../elements/Spinner';
|
||||||
import { Caption } from '../../typography/Caption';
|
import { Caption } from '../../typography/Caption';
|
||||||
import Heading from '../../typography/Heading';
|
import Heading from '../../typography/Heading';
|
||||||
import { DeviceWithVerification } from './types';
|
import { ExtendedDevice } from './types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
device: DeviceWithVerification;
|
device: ExtendedDevice;
|
||||||
saveDeviceName: (deviceName: string) => Promise<void>;
|
saveDeviceName: (deviceName: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,8 +72,8 @@ const DeviceDetails: React.FC<Props> = ({
|
||||||
id: 'application',
|
id: 'application',
|
||||||
heading: _t('Application'),
|
heading: _t('Application'),
|
||||||
values: [
|
values: [
|
||||||
{ label: _t('Name'), value: device.clientName },
|
{ label: _t('Name'), value: device.appName },
|
||||||
{ label: _t('Version'), value: device.clientVersion },
|
{ label: _t('Version'), value: device.appVersion },
|
||||||
{ label: _t('URL'), value: device.url },
|
{ label: _t('URL'), value: device.url },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,16 +21,16 @@ import { _t } from "../../../../languageHandler";
|
||||||
import { formatDate, formatRelativeTime } from "../../../../DateUtils";
|
import { formatDate, formatRelativeTime } from "../../../../DateUtils";
|
||||||
import Heading from "../../typography/Heading";
|
import Heading from "../../typography/Heading";
|
||||||
import { INACTIVE_DEVICE_AGE_DAYS, isDeviceInactive } from "./filter";
|
import { INACTIVE_DEVICE_AGE_DAYS, isDeviceInactive } from "./filter";
|
||||||
import { DeviceWithVerification } from "./types";
|
import { ExtendedDevice } from "./types";
|
||||||
import { DeviceType } from "./DeviceType";
|
import { DeviceTypeIcon } from "./DeviceTypeIcon";
|
||||||
export interface DeviceTileProps {
|
export interface DeviceTileProps {
|
||||||
device: DeviceWithVerification;
|
device: ExtendedDevice;
|
||||||
isSelected?: boolean;
|
isSelected?: boolean;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DeviceTileName: React.FC<{ device: DeviceWithVerification }> = ({ device }) => {
|
const DeviceTileName: React.FC<{ device: ExtendedDevice }> = ({ device }) => {
|
||||||
return <Heading size='h4'>
|
return <Heading size='h4'>
|
||||||
{ device.display_name || device.device_id }
|
{ device.display_name || device.device_id }
|
||||||
</Heading>;
|
</Heading>;
|
||||||
|
@ -48,7 +48,7 @@ const formatLastActivity = (timestamp: number, now = new Date().getTime()): stri
|
||||||
return formatRelativeTime(new Date(timestamp));
|
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);
|
const isInactive = isDeviceInactive(device);
|
||||||
|
|
||||||
if (!isInactive) {
|
if (!isInactive) {
|
||||||
|
@ -89,7 +89,11 @@ const DeviceTile: React.FC<DeviceTileProps> = ({
|
||||||
];
|
];
|
||||||
|
|
||||||
return <div className="mx_DeviceTile" data-testid={`device-tile-${device.device_id}`}>
|
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}>
|
<div className="mx_DeviceTile_info" onClick={onClick}>
|
||||||
<DeviceTileName device={device} />
|
<DeviceTileName device={device} />
|
||||||
<div className="mx_DeviceTile_metadata">
|
<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 VerifiedIcon } from '../../../../../res/img/e2e/verified.svg';
|
||||||
import { Icon as UnverifiedIcon } from '../../../../../res/img/e2e/warning.svg';
|
import { Icon as UnverifiedIcon } from '../../../../../res/img/e2e/warning.svg';
|
||||||
import { _t } from '../../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
import { DeviceWithVerification } from './types';
|
import { ExtendedDevice } from './types';
|
||||||
|
import { DeviceType } from '../../../../utils/device/parseUserAgent';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isVerified?: DeviceWithVerification['isVerified'];
|
isVerified?: ExtendedDevice['isVerified'];
|
||||||
isSelected?: boolean;
|
isSelected?: boolean;
|
||||||
|
deviceType?: DeviceType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeviceType: React.FC<Props> = ({ isVerified, isSelected }) => (
|
export const DeviceTypeIcon: React.FC<Props> = ({
|
||||||
<div className={classNames('mx_DeviceType', {
|
isVerified,
|
||||||
mx_DeviceType_selected: isSelected,
|
isSelected,
|
||||||
|
deviceType,
|
||||||
|
}) => (
|
||||||
|
<div className={classNames('mx_DeviceTypeIcon', {
|
||||||
|
mx_DeviceTypeIcon_selected: isSelected,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{ /* TODO(kerrya) all devices have an unknown type until PSG-650 */ }
|
{ /* TODO(kerrya) all devices have an unknown type until PSG-650 */ }
|
||||||
<UnknownDeviceIcon
|
<UnknownDeviceIcon
|
||||||
className='mx_DeviceType_deviceIcon'
|
className='mx_DeviceTypeIcon_deviceIcon'
|
||||||
role='img'
|
role='img'
|
||||||
aria-label={_t('Unknown device type')}
|
aria-label={_t('Unknown device type')}
|
||||||
/>
|
/>
|
||||||
{
|
{
|
||||||
isVerified
|
isVerified
|
||||||
? <VerifiedIcon
|
? <VerifiedIcon
|
||||||
className={classNames('mx_DeviceType_verificationIcon', 'verified')}
|
className={classNames('mx_DeviceTypeIcon_verificationIcon', 'verified')}
|
||||||
role='img'
|
role='img'
|
||||||
aria-label={_t('Verified')}
|
aria-label={_t('Verified')}
|
||||||
/>
|
/>
|
||||||
: <UnverifiedIcon
|
: <UnverifiedIcon
|
||||||
className={classNames('mx_DeviceType_verificationIcon', 'unverified')}
|
className={classNames('mx_DeviceTypeIcon_verificationIcon', 'unverified')}
|
||||||
role='img'
|
role='img'
|
||||||
aria-label={_t('Unverified')}
|
aria-label={_t('Unverified')}
|
||||||
/>
|
/>
|
|
@ -21,11 +21,11 @@ import AccessibleButton from '../../elements/AccessibleButton';
|
||||||
import DeviceSecurityCard from './DeviceSecurityCard';
|
import DeviceSecurityCard from './DeviceSecurityCard';
|
||||||
import {
|
import {
|
||||||
DeviceSecurityVariation,
|
DeviceSecurityVariation,
|
||||||
DeviceWithVerification,
|
ExtendedDevice,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
device: DeviceWithVerification;
|
device: ExtendedDevice;
|
||||||
onVerifyDevice?: () => void;
|
onVerifyDevice?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ import SelectableDeviceTile from './SelectableDeviceTile';
|
||||||
import {
|
import {
|
||||||
DevicesDictionary,
|
DevicesDictionary,
|
||||||
DeviceSecurityVariation,
|
DeviceSecurityVariation,
|
||||||
DeviceWithVerification,
|
ExtendedDevice,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { DevicesState } from './useOwnDevices';
|
import { DevicesState } from './useOwnDevices';
|
||||||
import FilteredDeviceListHeader from './FilteredDeviceListHeader';
|
import FilteredDeviceListHeader from './FilteredDeviceListHeader';
|
||||||
|
@ -42,27 +42,27 @@ interface Props {
|
||||||
devices: DevicesDictionary;
|
devices: DevicesDictionary;
|
||||||
pushers: IPusher[];
|
pushers: IPusher[];
|
||||||
localNotificationSettings: Map<string, LocalNotificationSettings>;
|
localNotificationSettings: Map<string, LocalNotificationSettings>;
|
||||||
expandedDeviceIds: DeviceWithVerification['device_id'][];
|
expandedDeviceIds: ExtendedDevice['device_id'][];
|
||||||
signingOutDeviceIds: DeviceWithVerification['device_id'][];
|
signingOutDeviceIds: ExtendedDevice['device_id'][];
|
||||||
selectedDeviceIds: DeviceWithVerification['device_id'][];
|
selectedDeviceIds: ExtendedDevice['device_id'][];
|
||||||
filter?: DeviceSecurityVariation;
|
filter?: DeviceSecurityVariation;
|
||||||
onFilterChange: (filter: DeviceSecurityVariation | undefined) => void;
|
onFilterChange: (filter: DeviceSecurityVariation | undefined) => void;
|
||||||
onDeviceExpandToggle: (deviceId: DeviceWithVerification['device_id']) => void;
|
onDeviceExpandToggle: (deviceId: ExtendedDevice['device_id']) => void;
|
||||||
onSignOutDevices: (deviceIds: DeviceWithVerification['device_id'][]) => void;
|
onSignOutDevices: (deviceIds: ExtendedDevice['device_id'][]) => void;
|
||||||
saveDeviceName: DevicesState['saveDeviceName'];
|
saveDeviceName: DevicesState['saveDeviceName'];
|
||||||
onRequestDeviceVerification?: (deviceId: DeviceWithVerification['device_id']) => void;
|
onRequestDeviceVerification?: (deviceId: ExtendedDevice['device_id']) => void;
|
||||||
setPushNotifications: (deviceId: string, enabled: boolean) => Promise<void>;
|
setPushNotifications: (deviceId: string, enabled: boolean) => Promise<void>;
|
||||||
setSelectedDeviceIds: (deviceIds: DeviceWithVerification['device_id'][]) => void;
|
setSelectedDeviceIds: (deviceIds: ExtendedDevice['device_id'][]) => void;
|
||||||
supportsMSC3881?: boolean | undefined;
|
supportsMSC3881?: boolean | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDeviceSelected = (
|
const isDeviceSelected = (
|
||||||
deviceId: DeviceWithVerification['device_id'],
|
deviceId: ExtendedDevice['device_id'],
|
||||||
selectedDeviceIds: DeviceWithVerification['device_id'][],
|
selectedDeviceIds: ExtendedDevice['device_id'][],
|
||||||
) => selectedDeviceIds.includes(deviceId);
|
) => selectedDeviceIds.includes(deviceId);
|
||||||
|
|
||||||
// devices without timestamp metadata should be sorted last
|
// 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);
|
(right.last_seen_ts || 0) - (left.last_seen_ts || 0);
|
||||||
|
|
||||||
const getFilteredSortedDevices = (devices: DevicesDictionary, filter?: DeviceSecurityVariation) =>
|
const getFilteredSortedDevices = (devices: DevicesDictionary, filter?: DeviceSecurityVariation) =>
|
||||||
|
@ -149,7 +149,7 @@ const NoResults: React.FC<NoResultsProps> = ({ filter, clearFilter }) =>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
const DeviceListItem: React.FC<{
|
const DeviceListItem: React.FC<{
|
||||||
device: DeviceWithVerification;
|
device: ExtendedDevice;
|
||||||
pusher?: IPusher | undefined;
|
pusher?: IPusher | undefined;
|
||||||
localNotificationSettings?: LocalNotificationSettings | undefined;
|
localNotificationSettings?: LocalNotificationSettings | undefined;
|
||||||
isExpanded: boolean;
|
isExpanded: boolean;
|
||||||
|
@ -227,11 +227,11 @@ export const FilteredDeviceList =
|
||||||
}: Props, ref: ForwardedRef<HTMLDivElement>) => {
|
}: Props, ref: ForwardedRef<HTMLDivElement>) => {
|
||||||
const sortedDevices = getFilteredSortedDevices(devices, filter);
|
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);
|
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)) {
|
if (isDeviceSelected(deviceId, selectedDeviceIds)) {
|
||||||
// remove from selection
|
// remove from selection
|
||||||
setSelectedDeviceIds(selectedDeviceIds.filter(id => id !== deviceId));
|
setSelectedDeviceIds(selectedDeviceIds.filter(id => id !== deviceId));
|
||||||
|
|
|
@ -23,13 +23,13 @@ import DeviceSecurityCard from './DeviceSecurityCard';
|
||||||
import { filterDevicesBySecurityRecommendation, INACTIVE_DEVICE_AGE_DAYS } from './filter';
|
import { filterDevicesBySecurityRecommendation, INACTIVE_DEVICE_AGE_DAYS } from './filter';
|
||||||
import {
|
import {
|
||||||
DeviceSecurityVariation,
|
DeviceSecurityVariation,
|
||||||
DeviceWithVerification,
|
ExtendedDevice,
|
||||||
DevicesDictionary,
|
DevicesDictionary,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
devices: DevicesDictionary;
|
devices: DevicesDictionary;
|
||||||
currentDeviceId: DeviceWithVerification['device_id'];
|
currentDeviceId: ExtendedDevice['device_id'];
|
||||||
goToFilteredList: (filter: DeviceSecurityVariation) => void;
|
goToFilteredList: (filter: DeviceSecurityVariation) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ const SecurityRecommendations: React.FC<Props> = ({
|
||||||
currentDeviceId,
|
currentDeviceId,
|
||||||
goToFilteredList,
|
goToFilteredList,
|
||||||
}) => {
|
}) => {
|
||||||
const devicesArray = Object.values<DeviceWithVerification>(devices);
|
const devicesArray = Object.values<ExtendedDevice>(devices);
|
||||||
|
|
||||||
const unverifiedDevicesCount = filterDevicesBySecurityRecommendation(
|
const unverifiedDevicesCount = filterDevicesBySecurityRecommendation(
|
||||||
devicesArray,
|
devicesArray,
|
||||||
|
|
|
@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
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;
|
const MS_DAY = 24 * 60 * 60 * 1000;
|
||||||
export const INACTIVE_DEVICE_AGE_MS = 7.776e+9; // 90 days
|
export const INACTIVE_DEVICE_AGE_MS = 7.776e+9; // 90 days
|
||||||
|
@ -32,7 +32,7 @@ const filters: Record<DeviceSecurityVariation, DeviceFilterCondition> = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const filterDevicesBySecurityRecommendation = (
|
export const filterDevicesBySecurityRecommendation = (
|
||||||
devices: DeviceWithVerification[],
|
devices: ExtendedDevice[],
|
||||||
securityVariations: DeviceSecurityVariation[],
|
securityVariations: DeviceSecurityVariation[],
|
||||||
) => {
|
) => {
|
||||||
const activeFilters = securityVariations.map(variation => filters[variation]);
|
const activeFilters = securityVariations.map(variation => filters[variation]);
|
||||||
|
|
|
@ -16,14 +16,17 @@ limitations under the License.
|
||||||
|
|
||||||
import { IMyDevice } from "matrix-js-sdk/src/matrix";
|
import { IMyDevice } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
|
import { ExtendedDeviceInformation } from "../../../../utils/device/parseUserAgent";
|
||||||
|
|
||||||
export type DeviceWithVerification = IMyDevice & { isVerified: boolean | null };
|
export type DeviceWithVerification = IMyDevice & { isVerified: boolean | null };
|
||||||
export type ExtendedDeviceInfo = {
|
export type ExtendedDeviceAppInfo = {
|
||||||
clientName?: string;
|
// eg Element Web
|
||||||
clientVersion?: string;
|
appName?: string;
|
||||||
|
appVersion?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
};
|
};
|
||||||
export type ExtendedDevice = DeviceWithVerification & ExtendedDeviceInfo;
|
export type ExtendedDevice = DeviceWithVerification & ExtendedDeviceAppInfo & ExtendedDeviceInformation;
|
||||||
export type DevicesDictionary = Record<DeviceWithVerification['device_id'], ExtendedDevice>;
|
export type DevicesDictionary = Record<ExtendedDevice['device_id'], ExtendedDevice>;
|
||||||
|
|
||||||
export enum DeviceSecurityVariation {
|
export enum DeviceSecurityVariation {
|
||||||
Verified = 'Verified',
|
Verified = 'Verified',
|
||||||
|
|
|
@ -24,6 +24,7 @@ import {
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
PUSHER_DEVICE_ID,
|
PUSHER_DEVICE_ID,
|
||||||
PUSHER_ENABLED,
|
PUSHER_ENABLED,
|
||||||
|
UNSTABLE_MSC3852_LAST_SEEN_UA,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} 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 { 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 MatrixClientContext from "../../../../contexts/MatrixClientContext";
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t } from "../../../../languageHandler";
|
||||||
import { getDeviceClientInformation } from "../../../../utils/device/clientInformation";
|
import { getDeviceClientInformation } from "../../../../utils/device/clientInformation";
|
||||||
import { DevicesDictionary, DeviceWithVerification, ExtendedDeviceInfo } from "./types";
|
import { DevicesDictionary, ExtendedDevice, ExtendedDeviceAppInfo } from "./types";
|
||||||
import { useEventEmitter } from "../../../../hooks/useEventEmitter";
|
import { useEventEmitter } from "../../../../hooks/useEventEmitter";
|
||||||
|
import { parseUserAgent } from "../../../../utils/device/parseUserAgent";
|
||||||
|
|
||||||
const isDeviceVerified = (
|
const isDeviceVerified = (
|
||||||
matrixClient: MatrixClient,
|
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);
|
const { name, version, url } = getDeviceClientInformation(matrixClient, device.device_id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
clientName: name,
|
appName: name,
|
||||||
clientVersion: version,
|
appVersion: version,
|
||||||
url,
|
url,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -87,6 +89,7 @@ const fetchDevicesWithVerification = async (
|
||||||
...device,
|
...device,
|
||||||
isVerified: isDeviceVerified(matrixClient, crossSigningInfo, device),
|
isVerified: isDeviceVerified(matrixClient, crossSigningInfo, device),
|
||||||
...parseDeviceExtendedInformation(matrixClient, device),
|
...parseDeviceExtendedInformation(matrixClient, device),
|
||||||
|
...parseUserAgent(device[UNSTABLE_MSC3852_LAST_SEEN_UA.name]),
|
||||||
},
|
},
|
||||||
}), {});
|
}), {});
|
||||||
|
|
||||||
|
@ -104,10 +107,10 @@ export type DevicesState = {
|
||||||
currentDeviceId: string;
|
currentDeviceId: string;
|
||||||
isLoadingDeviceList: boolean;
|
isLoadingDeviceList: boolean;
|
||||||
// not provided when current session cannot request verification
|
// 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>;
|
refreshDevices: () => Promise<void>;
|
||||||
saveDeviceName: (deviceId: DeviceWithVerification['device_id'], deviceName: string) => Promise<void>;
|
saveDeviceName: (deviceId: ExtendedDevice['device_id'], deviceName: string) => Promise<void>;
|
||||||
setPushNotifications: (deviceId: DeviceWithVerification['device_id'], enabled: boolean) => Promise<void>;
|
setPushNotifications: (deviceId: ExtendedDevice['device_id'], enabled: boolean) => Promise<void>;
|
||||||
error?: OwnDevicesError;
|
error?: OwnDevicesError;
|
||||||
supportsMSC3881?: boolean | undefined;
|
supportsMSC3881?: boolean | undefined;
|
||||||
};
|
};
|
||||||
|
@ -189,7 +192,7 @@ export const useOwnDevices = (): DevicesState => {
|
||||||
const isCurrentDeviceVerified = !!devices[currentDeviceId]?.isVerified;
|
const isCurrentDeviceVerified = !!devices[currentDeviceId]?.isVerified;
|
||||||
|
|
||||||
const requestDeviceVerification = isCurrentDeviceVerified && userId
|
const requestDeviceVerification = isCurrentDeviceVerified && userId
|
||||||
? async (deviceId: DeviceWithVerification['device_id']) => {
|
? async (deviceId: ExtendedDevice['device_id']) => {
|
||||||
return await matrixClient.requestVerification(
|
return await matrixClient.requestVerification(
|
||||||
userId,
|
userId,
|
||||||
[deviceId],
|
[deviceId],
|
||||||
|
@ -198,7 +201,7 @@ export const useOwnDevices = (): DevicesState => {
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const saveDeviceName = useCallback(
|
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];
|
const device = devices[deviceId];
|
||||||
|
|
||||||
// no change
|
// no change
|
||||||
|
@ -219,7 +222,7 @@ export const useOwnDevices = (): DevicesState => {
|
||||||
}, [matrixClient, devices, refreshDevices]);
|
}, [matrixClient, devices, refreshDevices]);
|
||||||
|
|
||||||
const setPushNotifications = useCallback(
|
const setPushNotifications = useCallback(
|
||||||
async (deviceId: DeviceWithVerification['device_id'], enabled: boolean): Promise<void> => {
|
async (deviceId: ExtendedDevice['device_id'], enabled: boolean): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const pusher = pushers.find(pusher => pusher[PUSHER_DEVICE_ID.name] === deviceId);
|
const pusher = pushers.find(pusher => pusher[PUSHER_DEVICE_ID.name] === deviceId);
|
||||||
if (pusher) {
|
if (pusher) {
|
||||||
|
|
|
@ -29,7 +29,7 @@ import { useOwnDevices } from '../../devices/useOwnDevices';
|
||||||
import { FilteredDeviceList } from '../../devices/FilteredDeviceList';
|
import { FilteredDeviceList } from '../../devices/FilteredDeviceList';
|
||||||
import CurrentDeviceSection from '../../devices/CurrentDeviceSection';
|
import CurrentDeviceSection from '../../devices/CurrentDeviceSection';
|
||||||
import SecurityRecommendations from '../../devices/SecurityRecommendations';
|
import SecurityRecommendations from '../../devices/SecurityRecommendations';
|
||||||
import { DeviceSecurityVariation, DeviceWithVerification } from '../../devices/types';
|
import { DeviceSecurityVariation, ExtendedDevice } from '../../devices/types';
|
||||||
import { deleteDevicesWithInteractiveAuth } from '../../devices/deleteDevices';
|
import { deleteDevicesWithInteractiveAuth } from '../../devices/deleteDevices';
|
||||||
import SettingsTab from '../SettingsTab';
|
import SettingsTab from '../SettingsTab';
|
||||||
|
|
||||||
|
@ -38,10 +38,10 @@ const useSignOut = (
|
||||||
onSignoutResolvedCallback: () => Promise<void>,
|
onSignoutResolvedCallback: () => Promise<void>,
|
||||||
): {
|
): {
|
||||||
onSignOutCurrentDevice: () => void;
|
onSignOutCurrentDevice: () => void;
|
||||||
onSignOutOtherDevices: (deviceIds: DeviceWithVerification['device_id'][]) => Promise<void>;
|
onSignOutOtherDevices: (deviceIds: ExtendedDevice['device_id'][]) => Promise<void>;
|
||||||
signingOutDeviceIds: DeviceWithVerification['device_id'][];
|
signingOutDeviceIds: ExtendedDevice['device_id'][];
|
||||||
} => {
|
} => {
|
||||||
const [signingOutDeviceIds, setSigningOutDeviceIds] = useState<DeviceWithVerification['device_id'][]>([]);
|
const [signingOutDeviceIds, setSigningOutDeviceIds] = useState<ExtendedDevice['device_id'][]>([]);
|
||||||
|
|
||||||
const onSignOutCurrentDevice = () => {
|
const onSignOutCurrentDevice = () => {
|
||||||
Modal.createDialog(
|
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) {
|
if (!deviceIds.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -96,8 +96,8 @@ const SessionManagerTab: React.FC = () => {
|
||||||
supportsMSC3881,
|
supportsMSC3881,
|
||||||
} = useOwnDevices();
|
} = useOwnDevices();
|
||||||
const [filter, setFilter] = useState<DeviceSecurityVariation>();
|
const [filter, setFilter] = useState<DeviceSecurityVariation>();
|
||||||
const [expandedDeviceIds, setExpandedDeviceIds] = useState<DeviceWithVerification['device_id'][]>([]);
|
const [expandedDeviceIds, setExpandedDeviceIds] = useState<ExtendedDevice['device_id'][]>([]);
|
||||||
const [selectedDeviceIds, setSelectedDeviceIds] = useState<DeviceWithVerification['device_id'][]>([]);
|
const [selectedDeviceIds, setSelectedDeviceIds] = useState<ExtendedDevice['device_id'][]>([]);
|
||||||
const filteredDeviceListRef = useRef<HTMLDivElement>(null);
|
const filteredDeviceListRef = useRef<HTMLDivElement>(null);
|
||||||
const scrollIntoViewTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
const scrollIntoViewTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ const SessionManagerTab: React.FC = () => {
|
||||||
const userId = matrixClient.getUserId();
|
const userId = matrixClient.getUserId();
|
||||||
const currentUserMember = userId && matrixClient.getUser(userId) || undefined;
|
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)) {
|
if (expandedDeviceIds.includes(deviceId)) {
|
||||||
setExpandedDeviceIds(expandedDeviceIds.filter(id => id !== deviceId));
|
setExpandedDeviceIds(expandedDeviceIds.filter(id => id !== deviceId));
|
||||||
} else {
|
} 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) {
|
if (!requestDeviceVerification) {
|
||||||
return;
|
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"
|
data-testid="device-tile-device_1"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_DeviceType"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
aria-label="Unknown device type"
|
||||||
class="mx_DeviceType_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceType_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -235,16 +235,16 @@ exports[`<DevicesPanel /> renders device panel with devices 1`] = `
|
||||||
data-testid="device-tile-device_2"
|
data-testid="device-tile-device_2"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_DeviceType"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
aria-label="Unknown device type"
|
||||||
class="mx_DeviceType_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceType_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -317,16 +317,16 @@ exports[`<DevicesPanel /> renders device panel with devices 1`] = `
|
||||||
data-testid="device-tile-device_3"
|
data-testid="device-tile-device_3"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_DeviceType"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
aria-label="Unknown device type"
|
||||||
class="mx_DeviceType_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceType_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { fireEvent, render } from '@testing-library/react';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
|
|
||||||
import CurrentDeviceSection from '../../../../../src/components/views/settings/devices/CurrentDeviceSection';
|
import CurrentDeviceSection from '../../../../../src/components/views/settings/devices/CurrentDeviceSection';
|
||||||
|
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
|
||||||
|
|
||||||
describe('<CurrentDeviceSection />', () => {
|
describe('<CurrentDeviceSection />', () => {
|
||||||
const deviceId = 'alices_device';
|
const deviceId = 'alices_device';
|
||||||
|
@ -26,10 +27,12 @@ describe('<CurrentDeviceSection />', () => {
|
||||||
const alicesVerifiedDevice = {
|
const alicesVerifiedDevice = {
|
||||||
device_id: deviceId,
|
device_id: deviceId,
|
||||||
isVerified: false,
|
isVerified: false,
|
||||||
|
deviceType: DeviceType.Unknown,
|
||||||
};
|
};
|
||||||
const alicesUnverifiedDevice = {
|
const alicesUnverifiedDevice = {
|
||||||
device_id: deviceId,
|
device_id: deviceId,
|
||||||
isVerified: false,
|
isVerified: false,
|
||||||
|
deviceType: DeviceType.Unknown,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { fireEvent, render, RenderResult } from '@testing-library/react';
|
||||||
|
|
||||||
import { DeviceDetailHeading } from '../../../../../src/components/views/settings/devices/DeviceDetailHeading';
|
import { DeviceDetailHeading } from '../../../../../src/components/views/settings/devices/DeviceDetailHeading';
|
||||||
import { flushPromisesWithFakeTimers } from '../../../../test-utils';
|
import { flushPromisesWithFakeTimers } from '../../../../test-utils';
|
||||||
|
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
|
||||||
|
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@ describe('<DeviceDetailHeading />', () => {
|
||||||
device_id: '123',
|
device_id: '123',
|
||||||
display_name: 'My device',
|
display_name: 'My device',
|
||||||
isVerified: true,
|
isVerified: true,
|
||||||
|
deviceType: DeviceType.Unknown,
|
||||||
};
|
};
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
device,
|
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 DeviceDetails from '../../../../../src/components/views/settings/devices/DeviceDetails';
|
||||||
import { mkPusher } from '../../../../test-utils/test-utils';
|
import { mkPusher } from '../../../../test-utils/test-utils';
|
||||||
|
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
|
||||||
|
|
||||||
describe('<DeviceDetails />', () => {
|
describe('<DeviceDetails />', () => {
|
||||||
const baseDevice = {
|
const baseDevice = {
|
||||||
device_id: 'my-device',
|
device_id: 'my-device',
|
||||||
isVerified: false,
|
isVerified: false,
|
||||||
|
deviceType: DeviceType.Unknown,
|
||||||
};
|
};
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
device: baseDevice,
|
device: baseDevice,
|
||||||
|
@ -58,7 +60,7 @@ describe('<DeviceDetails />', () => {
|
||||||
display_name: 'My Device',
|
display_name: 'My Device',
|
||||||
last_seen_ip: '123.456.789',
|
last_seen_ip: '123.456.789',
|
||||||
last_seen_ts: now - 60000000,
|
last_seen_ts: now - 60000000,
|
||||||
clientName: 'Element Web',
|
appName: 'Element Web',
|
||||||
};
|
};
|
||||||
const { container } = render(getComponent({ device }));
|
const { container } = render(getComponent({ device }));
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
|
|
|
@ -19,12 +19,14 @@ import { render } from '@testing-library/react';
|
||||||
import { IMyDevice } from 'matrix-js-sdk/src/matrix';
|
import { IMyDevice } from 'matrix-js-sdk/src/matrix';
|
||||||
|
|
||||||
import DeviceTile from '../../../../../src/components/views/settings/devices/DeviceTile';
|
import DeviceTile from '../../../../../src/components/views/settings/devices/DeviceTile';
|
||||||
|
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
|
||||||
|
|
||||||
describe('<DeviceTile />', () => {
|
describe('<DeviceTile />', () => {
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
device: {
|
device: {
|
||||||
device_id: '123',
|
device_id: '123',
|
||||||
isVerified: false,
|
isVerified: false,
|
||||||
|
deviceType: DeviceType.Unknown,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const getComponent = (props = {}) => (
|
const getComponent = (props = {}) => (
|
||||||
|
|
|
@ -17,15 +17,15 @@ limitations under the License.
|
||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import React from '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 = {
|
const defaultProps = {
|
||||||
isVerified: false,
|
isVerified: false,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
};
|
};
|
||||||
const getComponent = (props = {}) =>
|
const getComponent = (props = {}) =>
|
||||||
<DeviceType {...defaultProps} {...props} />;
|
<DeviceTypeIcon {...defaultProps} {...props} />;
|
||||||
|
|
||||||
it('renders an unverified device', () => {
|
it('renders an unverified device', () => {
|
||||||
const { container } = render(getComponent());
|
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 { FilteredDeviceList } from '../../../../../src/components/views/settings/devices/FilteredDeviceList';
|
||||||
import { DeviceSecurityVariation } from '../../../../../src/components/views/settings/devices/types';
|
import { DeviceSecurityVariation } from '../../../../../src/components/views/settings/devices/types';
|
||||||
import { flushPromises, mockPlatformPeg } from '../../../../test-utils';
|
import { flushPromises, mockPlatformPeg } from '../../../../test-utils';
|
||||||
|
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
|
||||||
|
|
||||||
mockPlatformPeg();
|
mockPlatformPeg();
|
||||||
|
|
||||||
|
@ -31,14 +32,26 @@ describe('<FilteredDeviceList />', () => {
|
||||||
last_seen_ip: '123.456.789',
|
last_seen_ip: '123.456.789',
|
||||||
display_name: 'My Device',
|
display_name: 'My Device',
|
||||||
isVerified: true,
|
isVerified: true,
|
||||||
|
deviceType: DeviceType.Unknown,
|
||||||
};
|
};
|
||||||
const unverifiedNoMetadata = { device_id: 'unverified-no-metadata', isVerified: false };
|
const unverifiedNoMetadata = {
|
||||||
const verifiedNoMetadata = { device_id: 'verified-no-metadata', isVerified: true };
|
device_id: 'unverified-no-metadata',
|
||||||
const hundredDaysOld = { device_id: '100-days-old', isVerified: true, last_seen_ts: Date.now() - (MS_DAY * 100) };
|
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 = {
|
const hundredDaysOldUnverified = {
|
||||||
device_id: 'unverified-100-days-old',
|
device_id: 'unverified-100-days-old',
|
||||||
isVerified: false,
|
isVerified: false,
|
||||||
last_seen_ts: Date.now() - (MS_DAY * 100),
|
last_seen_ts: Date.now() - (MS_DAY * 100),
|
||||||
|
deviceType: DeviceType.Unknown,
|
||||||
};
|
};
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
onFilterChange: jest.fn(),
|
onFilterChange: jest.fn(),
|
||||||
|
|
|
@ -19,6 +19,7 @@ import React from 'react';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
|
|
||||||
import SelectableDeviceTile from '../../../../../src/components/views/settings/devices/SelectableDeviceTile';
|
import SelectableDeviceTile from '../../../../../src/components/views/settings/devices/SelectableDeviceTile';
|
||||||
|
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
|
||||||
|
|
||||||
describe('<SelectableDeviceTile />', () => {
|
describe('<SelectableDeviceTile />', () => {
|
||||||
const device = {
|
const device = {
|
||||||
|
@ -26,6 +27,7 @@ describe('<SelectableDeviceTile />', () => {
|
||||||
device_id: 'my-device',
|
device_id: 'my-device',
|
||||||
last_seen_ip: '123.456.789',
|
last_seen_ip: '123.456.789',
|
||||||
isVerified: false,
|
isVerified: false,
|
||||||
|
deviceType: DeviceType.Unknown,
|
||||||
};
|
};
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
onClick: jest.fn(),
|
onClick: jest.fn(),
|
||||||
|
|
|
@ -151,16 +151,16 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
|
||||||
data-testid="device-tile-alices_device"
|
data-testid="device-tile-alices_device"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_DeviceType"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
aria-label="Unknown device type"
|
||||||
class="mx_DeviceType_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceType_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -267,16 +267,16 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
|
||||||
data-testid="device-tile-alices_device"
|
data-testid="device-tile-alices_device"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_DeviceType"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
aria-label="Unknown device type"
|
||||||
class="mx_DeviceType_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceType_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,16 +7,16 @@ exports[`<DeviceTile /> renders a device with no metadata 1`] = `
|
||||||
data-testid="device-tile-123"
|
data-testid="device-tile-123"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_DeviceType"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
aria-label="Unknown device type"
|
||||||
class="mx_DeviceType_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceType_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -58,16 +58,16 @@ exports[`<DeviceTile /> renders a verified device with no metadata 1`] = `
|
||||||
data-testid="device-tile-123"
|
data-testid="device-tile-123"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_DeviceType"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
aria-label="Unknown device type"
|
||||||
class="mx_DeviceType_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceType_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -109,16 +109,16 @@ exports[`<DeviceTile /> renders display name with a tooltip 1`] = `
|
||||||
data-testid="device-tile-123"
|
data-testid="device-tile-123"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_DeviceType"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
aria-label="Unknown device type"
|
||||||
class="mx_DeviceType_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceType_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -160,16 +160,16 @@ exports[`<DeviceTile /> separates metadata with a dot 1`] = `
|
||||||
data-testid="device-tile-123"
|
data-testid="device-tile-123"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_DeviceType"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
aria-label="Unknown device type"
|
||||||
class="mx_DeviceType_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceType_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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"
|
data-testid="device-tile-my-device"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_DeviceType"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
aria-label="Unknown device type"
|
||||||
class="mx_DeviceType_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceType_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -20,18 +20,38 @@ import {
|
||||||
import {
|
import {
|
||||||
DeviceSecurityVariation,
|
DeviceSecurityVariation,
|
||||||
} from "../../../../../src/components/views/settings/devices/types";
|
} from "../../../../../src/components/views/settings/devices/types";
|
||||||
|
import { DeviceType } from "../../../../../src/utils/device/parseUserAgent";
|
||||||
|
|
||||||
const MS_DAY = 86400000;
|
const MS_DAY = 86400000;
|
||||||
describe('filterDevicesBySecurityRecommendation()', () => {
|
describe('filterDevicesBySecurityRecommendation()', () => {
|
||||||
const unverifiedNoMetadata = { device_id: 'unverified-no-metadata', isVerified: false };
|
const unverifiedNoMetadata = {
|
||||||
const verifiedNoMetadata = { device_id: 'verified-no-metadata', isVerified: true };
|
device_id: 'unverified-no-metadata',
|
||||||
const hundredDaysOld = { device_id: '100-days-old', isVerified: true, last_seen_ts: Date.now() - (MS_DAY * 100) };
|
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 = {
|
const hundredDaysOldUnverified = {
|
||||||
device_id: 'unverified-100-days-old',
|
device_id: 'unverified-100-days-old',
|
||||||
isVerified: false,
|
isVerified: false,
|
||||||
last_seen_ts: Date.now() - (MS_DAY * 100),
|
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 = [
|
const devices = [
|
||||||
unverifiedNoMetadata,
|
unverifiedNoMetadata,
|
||||||
|
|
|
@ -44,7 +44,7 @@ import Modal from '../../../../../../src/Modal';
|
||||||
import LogoutDialog from '../../../../../../src/components/views/dialogs/LogoutDialog';
|
import LogoutDialog from '../../../../../../src/components/views/dialogs/LogoutDialog';
|
||||||
import {
|
import {
|
||||||
DeviceSecurityVariation,
|
DeviceSecurityVariation,
|
||||||
DeviceWithVerification,
|
ExtendedDevice,
|
||||||
} from '../../../../../../src/components/views/settings/devices/types';
|
} from '../../../../../../src/components/views/settings/devices/types';
|
||||||
import { INACTIVE_DEVICE_AGE_MS } from '../../../../../../src/components/views/settings/devices/filter';
|
import { INACTIVE_DEVICE_AGE_MS } from '../../../../../../src/components/views/settings/devices/filter';
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ describe('<SessionManagerTab />', () => {
|
||||||
|
|
||||||
const toggleDeviceDetails = (
|
const toggleDeviceDetails = (
|
||||||
getByTestId: ReturnType<typeof render>['getByTestId'],
|
getByTestId: ReturnType<typeof render>['getByTestId'],
|
||||||
deviceId: DeviceWithVerification['device_id'],
|
deviceId: ExtendedDevice['device_id'],
|
||||||
) => {
|
) => {
|
||||||
// open device detail
|
// open device detail
|
||||||
const tile = getByTestId(`device-tile-${deviceId}`);
|
const tile = getByTestId(`device-tile-${deviceId}`);
|
||||||
|
@ -114,7 +114,7 @@ describe('<SessionManagerTab />', () => {
|
||||||
|
|
||||||
const toggleDeviceSelection = (
|
const toggleDeviceSelection = (
|
||||||
getByTestId: ReturnType<typeof render>['getByTestId'],
|
getByTestId: ReturnType<typeof render>['getByTestId'],
|
||||||
deviceId: DeviceWithVerification['device_id'],
|
deviceId: ExtendedDevice['device_id'],
|
||||||
) => {
|
) => {
|
||||||
const checkbox = getByTestId(`device-tile-checkbox-${deviceId}`);
|
const checkbox = getByTestId(`device-tile-checkbox-${deviceId}`);
|
||||||
fireEvent.click(checkbox);
|
fireEvent.click(checkbox);
|
||||||
|
@ -135,7 +135,7 @@ describe('<SessionManagerTab />', () => {
|
||||||
|
|
||||||
const isDeviceSelected = (
|
const isDeviceSelected = (
|
||||||
getByTestId: ReturnType<typeof render>['getByTestId'],
|
getByTestId: ReturnType<typeof render>['getByTestId'],
|
||||||
deviceId: DeviceWithVerification['device_id'],
|
deviceId: ExtendedDevice['device_id'],
|
||||||
): boolean => !!(getByTestId(`device-tile-checkbox-${deviceId}`) as HTMLInputElement).checked;
|
): boolean => !!(getByTestId(`device-tile-checkbox-${deviceId}`) as HTMLInputElement).checked;
|
||||||
|
|
||||||
const isSelectAllChecked = (
|
const isSelectAllChecked = (
|
||||||
|
|
|
@ -94,16 +94,16 @@ exports[`<SessionManagerTab /> renders current session section with a verified s
|
||||||
data-testid="device-tile-alices_device"
|
data-testid="device-tile-alices_device"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_DeviceType"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
aria-label="Unknown device type"
|
||||||
class="mx_DeviceType_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-label="Verified"
|
aria-label="Verified"
|
||||||
class="mx_DeviceType_verificationIcon verified"
|
class="mx_DeviceTypeIcon_verificationIcon verified"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -196,16 +196,16 @@ exports[`<SessionManagerTab /> renders current session section with an unverifie
|
||||||
data-testid="device-tile-alices_device"
|
data-testid="device-tile-alices_device"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_DeviceType"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
aria-label="Unknown device type"
|
||||||
class="mx_DeviceType_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceType_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -298,16 +298,16 @@ exports[`<SessionManagerTab /> sets device verification status correctly 1`] = `
|
||||||
data-testid="device-tile-alices_device"
|
data-testid="device-tile-alices_device"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_DeviceType"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
aria-label="Unknown device type"
|
||||||
class="mx_DeviceType_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-label="Verified"
|
aria-label="Verified"
|
||||||
class="mx_DeviceType_verificationIcon verified"
|
class="mx_DeviceTypeIcon_verificationIcon verified"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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