import React, { useCallback, useState } from 'react'; import { Box, Button, config, Menu, Spinner, Text } from 'folds'; import { AuthDict, IMyDevice, MatrixError } from 'matrix-js-sdk'; import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCardStyle } from '../styles.css'; import { ActionUIA, ActionUIAFlowsLoader } from '../../../components/ActionUIA'; import { DeviceDeleteBtn, DeviceTile } from './DeviceTile'; import { AsyncState, AsyncStatus, useAsync } from '../../../hooks/useAsyncCallback'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useUIAMatrixError } from '../../../hooks/useUIAFlows'; import { DeviceVerificationStatus } from '../../../components/DeviceVerificationStatus'; import { VerifyOtherDeviceTile } from './Verification'; import { VerificationStatus } from '../../../hooks/useDeviceVerificationStatus'; import { useAuthMetadata } from '../../../hooks/useAuthMetaData'; import { withSearchParam } from '../../../pages/pathUtils'; import { useAccountManagementActions } from '../../../hooks/useAccountManagement'; import { SettingTile } from '../../../components/setting-tile'; type OtherDevicesProps = { devices: IMyDevice[]; refreshDeviceList: () => Promise; showVerification?: boolean; }; export function OtherDevices({ devices, refreshDeviceList, showVerification }: OtherDevicesProps) { const mx = useMatrixClient(); const crypto = mx.getCrypto(); const authMetadata = useAuthMetadata(); const accountManagementActions = useAccountManagementActions(); const [deleted, setDeleted] = useState>(new Set()); const handleDashboardOIDC = useCallback(() => { const authUrl = authMetadata?.account_management_uri ?? authMetadata?.issuer; if (!authUrl) return; window.open( withSearchParam(authUrl, { action: accountManagementActions.sessionsList, }), '_blank' ); }, [authMetadata, accountManagementActions]); const handleDeleteOIDC = useCallback( (deviceId: string) => { const authUrl = authMetadata?.account_management_uri ?? authMetadata?.issuer; if (!authUrl) return; window.open( withSearchParam(authUrl, { action: accountManagementActions.sessionEnd, device_id: deviceId, }), '_blank' ); }, [authMetadata, accountManagementActions] ); const handleToggleDelete = useCallback((deviceId: string) => { setDeleted((deviceIds) => { const newIds = new Set(deviceIds); if (newIds.has(deviceId)) { newIds.delete(deviceId); } else { newIds.add(deviceId); } return newIds; }); }, []); const [deleteState, setDeleteState] = useState>({ status: AsyncStatus.Idle, }); const deleteDevices = useAsync( useCallback( async (authDict?: AuthDict) => { await mx.deleteMultipleDevices(Array.from(deleted), authDict); }, [mx, deleted] ), useCallback( (state: typeof deleteState) => { if (state.status === AsyncStatus.Success) { setDeleted(new Set()); refreshDeviceList(); } setDeleteState(state); }, [refreshDeviceList] ) ); const [authData, deleteError] = useUIAMatrixError( deleteState.status === AsyncStatus.Error ? deleteState.error : undefined ); const deleting = deleteState.status === AsyncStatus.Loading || authData !== undefined; const handleCancelDelete = () => setDeleted(new Set()); const handleCancelAuth = useCallback(() => { setDeleteState({ status: AsyncStatus.Idle }); }, []); return devices.length > 0 ? ( <> Others {authMetadata && ( Open } /> )} {devices .sort((d1, d2) => { if (!d1.last_seen_ts || !d2.last_seen_ts) return 0; return d1.last_seen_ts < d2.last_seen_ts ? 1 : -1; }) .map((device) => ( ) : ( ) } /> {showVerification && crypto && ( {(status) => status === VerificationStatus.Unverified && ( ) } )} ))} {deleted.size > 0 && ( {deleteError ? ( Failed to logout devices! Please try again. {deleteError.message} ) : ( Logout from selected devices. ({deleted.size} selected) )} {authData && ( ( Authentication steps to perform this action are not supported by client. )} > {(ongoingFlow) => ( )} )} )} ) : null; }