feat: add other-user device sessions list with per-device verification

Adds a collapsible "Sessions" section to the user profile card that
appears when cross-signing is active and the profile belongs to another
user. Each session shows a colour-coded shield (green = verified, yellow
= unverified) and a "Verify" button for unverified devices that
initiates the SAS emoji flow via crypto.requestDeviceVerification.

New hook useOtherUserDevices fetches the target user's device list via
crypto.getUserDeviceInfo and reacts to CryptoEvent.DevicesUpdated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-23 19:44:22 -04:00
parent 6c9fd17f27
commit 9b18804927
2 changed files with 213 additions and 2 deletions
+54
View File
@@ -0,0 +1,54 @@
import { useCallback, useEffect, useState } from 'react';
import { useMatrixClient } from './useMatrixClient';
import { useCrossSigningActive } from './useCrossSigning';
import { useDeviceListChange } from './useDeviceList';
export type UserDevice = {
deviceId: string;
displayName?: string;
};
export function useOtherUserDevices(userId: string): UserDevice[] | undefined {
const mx = useMatrixClient();
const crossSigningActive = useCrossSigningActive();
const [devices, setDevices] = useState<UserDevice[] | undefined>(undefined);
const fetchDevices = useCallback(async () => {
const crypto = mx.getCrypto();
if (!crypto || !crossSigningActive) {
setDevices(undefined);
return;
}
try {
const deviceMap = await crypto.getUserDeviceInfo([userId], true);
const userDevices = deviceMap.get(userId);
if (!userDevices) {
setDevices([]);
return;
}
setDevices(
Array.from(userDevices.values()).map((device) => ({
deviceId: device.deviceId,
displayName: device.displayName,
})),
);
} catch {
setDevices(undefined);
}
}, [mx, userId, crossSigningActive]);
useEffect(() => {
fetchDevices();
}, [fetchDevices]);
useDeviceListChange(
useCallback(
(userIds: string[]) => {
if (userIds.includes(userId)) fetchDevices();
},
[userId, fetchDevices],
),
);
return devices;
}