diff --git a/src/app/hooks/useDeviceList.js b/src/app/hooks/useDeviceList.js deleted file mode 100644 index 7daaad1f3..000000000 --- a/src/app/hooks/useDeviceList.js +++ /dev/null @@ -1,31 +0,0 @@ -/* eslint-disable import/prefer-default-export */ -import { useState, useEffect } from 'react'; -import { useMatrixClient } from './useMatrixClient'; - -export function useDeviceList() { - const mx = useMatrixClient(); - const [deviceList, setDeviceList] = useState(null); - - useEffect(() => { - let isMounted = true; - - const updateDevices = () => mx.getDevices().then((data) => { - if (!isMounted) return; - setDeviceList(data.devices || []); - }); - updateDevices(); - - const handleDevicesUpdate = (users) => { - if (users.includes(mx.getUserId())) { - updateDevices(); - } - }; - - mx.on('crypto.devicesUpdated', handleDevicesUpdate); - return () => { - mx.removeListener('crypto.devicesUpdated', handleDevicesUpdate); - isMounted = false; - }; - }, [mx]); - return deviceList; -} diff --git a/src/app/hooks/useDeviceList.ts b/src/app/hooks/useDeviceList.ts new file mode 100644 index 000000000..daec7cbe6 --- /dev/null +++ b/src/app/hooks/useDeviceList.ts @@ -0,0 +1,35 @@ +/* eslint-disable import/prefer-default-export */ +import { useState, useEffect } from 'react'; +import { CryptoEvent, IMyDevice } from 'matrix-js-sdk'; +import { CryptoEventHandlerMap } from 'matrix-js-sdk/lib/crypto'; +import { useMatrixClient } from './useMatrixClient'; + +export function useDeviceList() { + const mx = useMatrixClient(); + const [deviceList, setDeviceList] = useState(null); + + useEffect(() => { + let isMounted = true; + + const updateDevices = () => + mx.getDevices().then((data) => { + if (!isMounted) return; + setDeviceList(data.devices || []); + }); + updateDevices(); + + const handleDevicesUpdate: CryptoEventHandlerMap[CryptoEvent.DevicesUpdated] = (users) => { + const userId = mx.getUserId(); + if (userId && users.includes(userId)) { + updateDevices(); + } + }; + + mx.on(CryptoEvent.DevicesUpdated, handleDevicesUpdate); + return () => { + mx.removeListener(CryptoEvent.DevicesUpdated, handleDevicesUpdate); + isMounted = false; + }; + }, [mx]); + return deviceList; +} diff --git a/src/app/pages/client/SidebarNav.tsx b/src/app/pages/client/SidebarNav.tsx index fb6bd7422..110e46940 100644 --- a/src/app/pages/client/SidebarNav.tsx +++ b/src/app/pages/client/SidebarNav.tsx @@ -10,7 +10,15 @@ import { SidebarItemTooltip, SidebarItem, } from '../../components/sidebar'; -import { DirectTab, HomeTab, SpaceTabs, InboxTab, ExploreTab, UserTab } from './sidebar'; +import { + DirectTab, + HomeTab, + SpaceTabs, + InboxTab, + ExploreTab, + UserTab, + UnverifiedTab, +} from './sidebar'; import { openCreateRoom, openSearch } from '../../../client/action/navigation'; export function SidebarNav() { @@ -65,6 +73,8 @@ export function SidebarNav() { + + diff --git a/src/app/pages/client/sidebar/UnverifiedTab.css.ts b/src/app/pages/client/sidebar/UnverifiedTab.css.ts new file mode 100644 index 000000000..e8fe8c51b --- /dev/null +++ b/src/app/pages/client/sidebar/UnverifiedTab.css.ts @@ -0,0 +1,24 @@ +import { keyframes, style } from '@vanilla-extract/css'; +import { color, toRem } from 'folds'; + +const pushRight = keyframes({ + from: { + transform: `translateX(${toRem(2)}) scale(1)`, + }, + to: { + transform: 'translateX(0) scale(1)', + }, +}); + +export const UnverifiedTab = style({ + animationName: pushRight, + animationDuration: '400ms', + animationIterationCount: 30, + animationDirection: 'alternate', +}); + +export const UnverifiedAvatar = style({ + backgroundColor: color.Critical.Container, + color: color.Critical.OnContainer, + borderColor: color.Critical.ContainerLine, +}); diff --git a/src/app/pages/client/sidebar/UnverifiedTab.tsx b/src/app/pages/client/sidebar/UnverifiedTab.tsx new file mode 100644 index 000000000..402ffc2b8 --- /dev/null +++ b/src/app/pages/client/sidebar/UnverifiedTab.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { Badge, color, Icon, Icons, Text } from 'folds'; +import { openSettings } from '../../../../client/action/navigation'; +import { isCrossVerified } from '../../../../util/matrixUtil'; +import { + SidebarAvatar, + SidebarItem, + SidebarItemBadge, + SidebarItemTooltip, +} from '../../../components/sidebar'; +import { useDeviceList } from '../../../hooks/useDeviceList'; +import { tabText } from '../../../organisms/settings/Settings'; +import { useMatrixClient } from '../../../hooks/useMatrixClient'; +import * as css from './UnverifiedTab.css'; + +export function UnverifiedTab() { + const mx = useMatrixClient(); + const deviceList = useDeviceList(); + console.log(deviceList); + const unverified = deviceList?.filter( + (device) => isCrossVerified(mx, device.device_id) === false + ); + console.log(unverified); + + if (!unverified?.length) return null; + + return ( + + + {(triggerRef) => ( + openSettings(tabText.SECURITY)} + > + + + )} + + + + + {unverified.length} + + + + + ); +} diff --git a/src/app/pages/client/sidebar/index.ts b/src/app/pages/client/sidebar/index.ts index 63c5d4bb6..5da8780b1 100644 --- a/src/app/pages/client/sidebar/index.ts +++ b/src/app/pages/client/sidebar/index.ts @@ -4,3 +4,4 @@ export * from './SpaceTabs'; export * from './InboxTab'; export * from './ExploreTab'; export * from './UserTab'; +export * from './UnverifiedTab'; diff --git a/src/util/matrixUtil.js b/src/util/matrixUtil.js index 9f1d94212..7664c3a40 100644 --- a/src/util/matrixUtil.js +++ b/src/util/matrixUtil.js @@ -106,7 +106,7 @@ export function isCrossVerified(mx, deviceId) { const deviceInfo = mx.getStoredDevice(mx.getUserId(), deviceId); const deviceTrust = crossSignInfo.checkDeviceTrust(crossSignInfo, deviceInfo, false, true); return deviceTrust.isCrossSigningVerified(); - } catch { + } catch (e) { // device does not support encryption return null; }