Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Device manager - add foundation for extended device info (#9344)
Browse files Browse the repository at this point in the history
* 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 parsing
  • Loading branch information
Kerry authored Oct 5, 2022
1 parent 1032334 commit bd270b0
Show file tree
Hide file tree
Showing 34 changed files with 319 additions and 187 deletions.
2 changes: 1 addition & 1 deletion res/css/_components.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ 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;
/* creates space for verification icon to overlap */
padding: 0 $spacing-8 $spacing-8 0;
}

.mx_DeviceType_deviceIcon {
.mx_DeviceTypeIcon_deviceIcon {
--background-color: $system;
--icon-color: $secondary-content;

Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion res/css/views/settings/_DevicesPanel.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 5 additions & 3 deletions src/components/views/settings/DevicesPanelEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -153,25 +154,26 @@ 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) {
return <div className={classNames("mx_DevicesPanel_device", "mx_DevicesPanel_myDevice")}>
<div className="mx_DevicesPanel_deviceTrust">
<span className={"mx_DevicesPanel_icon mx_E2EIcon " + iconClass} />
</div>
<DeviceTile device={deviceWithVerification}>
<DeviceTile device={extendedDevice}>
{ buttons }
</DeviceTile>
</div>;
}

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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/components/views/settings/devices/DeviceDetailHeading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
}

Expand Down
4 changes: 2 additions & 2 deletions src/components/views/settings/devices/DeviceDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
],
},
Expand Down
16 changes: 10 additions & 6 deletions src/components/views/settings/devices/DeviceTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand All @@ -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) {
Expand Down Expand Up @@ -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">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
28 changes: 14 additions & 14 deletions src/components/views/settings/devices/FilteredDeviceList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import SelectableDeviceTile from './SelectableDeviceTile';
import {
DevicesDictionary,
DeviceSecurityVariation,
DeviceWithVerification,
ExtendedDevice,
} from './types';
import { DevicesState } from './useOwnDevices';
import FilteredDeviceListHeader from './FilteredDeviceListHeader';
Expand All @@ -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) =>
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions src/components/views/settings/devices/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]);
Expand Down
13 changes: 8 additions & 5 deletions src/components/views/settings/devices/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading

0 comments on commit bd270b0

Please sign in to comment.