From bd270b08dfc6bd604cd3dc47ed56e198d6bfdf6a Mon Sep 17 00:00:00 2001 From: Kerry Date: Wed, 5 Oct 2022 13:41:01 +0200 Subject: [PATCH] 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 parsing --- res/css/_components.pcss | 2 +- ...{_DeviceType.pcss => _DeviceTypeIcon.pcss} | 8 +-- res/css/views/settings/_DevicesPanel.pcss | 2 +- .../views/settings/DevicesPanelEntry.tsx | 8 ++- .../settings/devices/CurrentDeviceSection.tsx | 4 +- .../settings/devices/DeviceDetailHeading.tsx | 4 +- .../views/settings/devices/DeviceDetails.tsx | 4 +- .../views/settings/devices/DeviceTile.tsx | 16 +++-- .../{DeviceType.tsx => DeviceTypeIcon.tsx} | 22 ++++--- .../devices/DeviceVerificationStatusCard.tsx | 4 +- .../settings/devices/FilteredDeviceList.tsx | 28 ++++----- .../devices/SecurityRecommendations.tsx | 6 +- .../views/settings/devices/filter.ts | 6 +- .../views/settings/devices/types.ts | 13 +++-- .../views/settings/devices/useOwnDevices.ts | 23 ++++---- .../settings/tabs/user/SessionManagerTab.tsx | 18 +++--- src/utils/device/parseUserAgent.ts | 45 ++++++++++++++ .../__snapshots__/DevicesPanel-test.tsx.snap | 18 +++--- .../devices/CurrentDeviceSection-test.tsx | 3 + .../devices/DeviceDetailHeading-test.tsx | 2 + .../settings/devices/DeviceDetails-test.tsx | 4 +- .../settings/devices/DeviceTile-test.tsx | 2 + ...eType-test.tsx => DeviceTypeIcon-test.tsx} | 6 +- .../devices/FilteredDeviceList-test.tsx | 19 +++++- .../devices/SelectableDeviceTile-test.tsx | 2 + .../CurrentDeviceSection-test.tsx.snap | 12 ++-- .../__snapshots__/DeviceTile-test.tsx.snap | 24 ++++---- .../__snapshots__/DeviceType-test.tsx.snap | 58 ------------------- .../DeviceTypeIcon-test.tsx.snap | 58 +++++++++++++++++++ .../SelectableDeviceTile-test.tsx.snap | 6 +- .../views/settings/devices/filter-test.ts | 28 +++++++-- .../tabs/user/SessionManagerTab-test.tsx | 8 +-- .../SessionManagerTab-test.tsx.snap | 18 +++--- test/utils/device/parseUserAgent-test.ts | 25 ++++++++ 34 files changed, 319 insertions(+), 187 deletions(-) rename res/css/components/views/settings/devices/{_DeviceType.pcss => _DeviceTypeIcon.pcss} (91%) rename src/components/views/settings/devices/{DeviceType.tsx => DeviceTypeIcon.tsx} (70%) create mode 100644 src/utils/device/parseUserAgent.ts rename test/components/views/settings/devices/{DeviceType-test.tsx => DeviceTypeIcon-test.tsx} (86%) delete mode 100644 test/components/views/settings/devices/__snapshots__/DeviceType-test.tsx.snap create mode 100644 test/components/views/settings/devices/__snapshots__/DeviceTypeIcon-test.tsx.snap create mode 100644 test/utils/device/parseUserAgent-test.ts diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 9161942d87e..17fb679f247 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -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"; diff --git a/res/css/components/views/settings/devices/_DeviceType.pcss b/res/css/components/views/settings/devices/_DeviceTypeIcon.pcss similarity index 91% rename from res/css/components/views/settings/devices/_DeviceType.pcss rename to res/css/components/views/settings/devices/_DeviceTypeIcon.pcss index 66372bbdea0..546d4f7ea12 100644 --- a/res/css/components/views/settings/devices/_DeviceType.pcss +++ b/res/css/components/views/settings/devices/_DeviceTypeIcon.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; diff --git a/res/css/views/settings/_DevicesPanel.pcss b/res/css/views/settings/_DevicesPanel.pcss index 23a737c9779..8a7842d4d0f 100644 --- a/res/css/views/settings/_DevicesPanel.pcss +++ b/res/css/views/settings/_DevicesPanel.pcss @@ -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; diff --git a/src/components/views/settings/DevicesPanelEntry.tsx b/src/components/views/settings/DevicesPanelEntry.tsx index 0109c37b9ba..aa152826bf3 100644 --- a/src/components/views/settings/DevicesPanelEntry.tsx +++ b/src/components/views/settings/DevicesPanelEntry.tsx @@ -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 { ; - 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 {
- + { buttons } ; @@ -171,7 +173,7 @@ export default class DevicesPanelEntry extends React.Component { return (
- + { buttons }
diff --git a/src/components/views/settings/devices/CurrentDeviceSection.tsx b/src/components/views/settings/devices/CurrentDeviceSection.tsx index 615c9c69f06..fc58617d313 100644 --- a/src/components/views/settings/devices/CurrentDeviceSection.tsx +++ b/src/components/views/settings/devices/CurrentDeviceSection.tsx @@ -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; diff --git a/src/components/views/settings/devices/DeviceDetailHeading.tsx b/src/components/views/settings/devices/DeviceDetailHeading.tsx index dea79d3b23f..2673ef4e897 100644 --- a/src/components/views/settings/devices/DeviceDetailHeading.tsx +++ b/src/components/views/settings/devices/DeviceDetailHeading.tsx @@ -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; } diff --git a/src/components/views/settings/devices/DeviceDetails.tsx b/src/components/views/settings/devices/DeviceDetails.tsx index b87bfcef3c4..4ed50c07b7c 100644 --- a/src/components/views/settings/devices/DeviceDetails.tsx +++ b/src/components/views/settings/devices/DeviceDetails.tsx @@ -72,8 +72,8 @@ const DeviceDetails: React.FC = ({ 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 }, ], }, diff --git a/src/components/views/settings/devices/DeviceTile.tsx b/src/components/views/settings/devices/DeviceTile.tsx index bfeabfabb3b..4c8e2647513 100644 --- a/src/components/views/settings/devices/DeviceTile.tsx +++ b/src/components/views/settings/devices/DeviceTile.tsx @@ -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 { device.display_name || device.device_id } ; @@ -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 = ({ ]; return
- +
diff --git a/src/components/views/settings/devices/DeviceType.tsx b/src/components/views/settings/devices/DeviceTypeIcon.tsx similarity index 70% rename from src/components/views/settings/devices/DeviceType.tsx rename to src/components/views/settings/devices/DeviceTypeIcon.tsx index a0fbe75c565..03b921f711d 100644 --- a/src/components/views/settings/devices/DeviceType.tsx +++ b/src/components/views/settings/devices/DeviceTypeIcon.tsx @@ -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 = ({ isVerified, isSelected }) => ( -
= ({ + isVerified, + isSelected, + deviceType, +}) => ( +
{ /* TODO(kerrya) all devices have an unknown type until PSG-650 */ } { isVerified ? : diff --git a/src/components/views/settings/devices/DeviceVerificationStatusCard.tsx b/src/components/views/settings/devices/DeviceVerificationStatusCard.tsx index 11e806e54e4..127f5eedf60 100644 --- a/src/components/views/settings/devices/DeviceVerificationStatusCard.tsx +++ b/src/components/views/settings/devices/DeviceVerificationStatusCard.tsx @@ -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; } diff --git a/src/components/views/settings/devices/FilteredDeviceList.tsx b/src/components/views/settings/devices/FilteredDeviceList.tsx index 4cf7ac1a635..c2e8786052a 100644 --- a/src/components/views/settings/devices/FilteredDeviceList.tsx +++ b/src/components/views/settings/devices/FilteredDeviceList.tsx @@ -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; - 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; - 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 = ({ filter, clearFilter }) =>
; 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) => { 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)); diff --git a/src/components/views/settings/devices/SecurityRecommendations.tsx b/src/components/views/settings/devices/SecurityRecommendations.tsx index 3132eba38a3..ddeb2f2e2e8 100644 --- a/src/components/views/settings/devices/SecurityRecommendations.tsx +++ b/src/components/views/settings/devices/SecurityRecommendations.tsx @@ -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 = ({ currentDeviceId, goToFilteredList, }) => { - const devicesArray = Object.values(devices); + const devicesArray = Object.values(devices); const unverifiedDevicesCount = filterDevicesBySecurityRecommendation( devicesArray, diff --git a/src/components/views/settings/devices/filter.ts b/src/components/views/settings/devices/filter.ts index ad2bc92152c..05ceb9c6972 100644 --- a/src/components/views/settings/devices/filter.ts +++ b/src/components/views/settings/devices/filter.ts @@ -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 = { }; export const filterDevicesBySecurityRecommendation = ( - devices: DeviceWithVerification[], + devices: ExtendedDevice[], securityVariations: DeviceSecurityVariation[], ) => { const activeFilters = securityVariations.map(variation => filters[variation]); diff --git a/src/components/views/settings/devices/types.ts b/src/components/views/settings/devices/types.ts index 9543ac2b32e..3fa125a09f8 100644 --- a/src/components/views/settings/devices/types.ts +++ b/src/components/views/settings/devices/types.ts @@ -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; +export type ExtendedDevice = DeviceWithVerification & ExtendedDeviceAppInfo & ExtendedDeviceInformation; +export type DevicesDictionary = Record; export enum DeviceSecurityVariation { Verified = 'Verified', diff --git a/src/components/views/settings/devices/useOwnDevices.ts b/src/components/views/settings/devices/useOwnDevices.ts index 2441a63a2ba..c3b8cb0212a 100644 --- a/src/components/views/settings/devices/useOwnDevices.ts +++ b/src/components/views/settings/devices/useOwnDevices.ts @@ -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; + requestDeviceVerification?: (deviceId: ExtendedDevice['device_id']) => Promise; refreshDevices: () => Promise; - saveDeviceName: (deviceId: DeviceWithVerification['device_id'], deviceName: string) => Promise; - setPushNotifications: (deviceId: DeviceWithVerification['device_id'], enabled: boolean) => Promise; + saveDeviceName: (deviceId: ExtendedDevice['device_id'], deviceName: string) => Promise; + setPushNotifications: (deviceId: ExtendedDevice['device_id'], enabled: boolean) => Promise; 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 => { + async (deviceId: ExtendedDevice['device_id'], deviceName: string): Promise => { 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 => { + async (deviceId: ExtendedDevice['device_id'], enabled: boolean): Promise => { try { const pusher = pushers.find(pusher => pusher[PUSHER_DEVICE_ID.name] === deviceId); if (pusher) { diff --git a/src/components/views/settings/tabs/user/SessionManagerTab.tsx b/src/components/views/settings/tabs/user/SessionManagerTab.tsx index ed1d04a7546..2c94d5a5c2f 100644 --- a/src/components/views/settings/tabs/user/SessionManagerTab.tsx +++ b/src/components/views/settings/tabs/user/SessionManagerTab.tsx @@ -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, ): { onSignOutCurrentDevice: () => void; - onSignOutOtherDevices: (deviceIds: DeviceWithVerification['device_id'][]) => Promise; - signingOutDeviceIds: DeviceWithVerification['device_id'][]; + onSignOutOtherDevices: (deviceIds: ExtendedDevice['device_id'][]) => Promise; + signingOutDeviceIds: ExtendedDevice['device_id'][]; } => { - const [signingOutDeviceIds, setSigningOutDeviceIds] = useState([]); + const [signingOutDeviceIds, setSigningOutDeviceIds] = useState([]); 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(); - const [expandedDeviceIds, setExpandedDeviceIds] = useState([]); - const [selectedDeviceIds, setSelectedDeviceIds] = useState([]); + const [expandedDeviceIds, setExpandedDeviceIds] = useState([]); + const [selectedDeviceIds, setSelectedDeviceIds] = useState([]); const filteredDeviceListRef = useRef(null); const scrollIntoViewTimeoutRef = useRef>(); @@ -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; } diff --git a/src/utils/device/parseUserAgent.ts b/src/utils/device/parseUserAgent.ts new file mode 100644 index 00000000000..32c57b7624d --- /dev/null +++ b/src/utils/device/parseUserAgent.ts @@ -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, + }; +}; diff --git a/test/components/views/settings/__snapshots__/DevicesPanel-test.tsx.snap b/test/components/views/settings/__snapshots__/DevicesPanel-test.tsx.snap index df46340de37..05c0ca8c98d 100644 --- a/test/components/views/settings/__snapshots__/DevicesPanel-test.tsx.snap +++ b/test/components/views/settings/__snapshots__/DevicesPanel-test.tsx.snap @@ -112,16 +112,16 @@ exports[` renders device panel with devices 1`] = ` data-testid="device-tile-device_1" >