diff --git a/pkg/hicli/json-commands.go b/pkg/hicli/json-commands.go index c09d4c0..1002d56 100644 --- a/pkg/hicli/json-commands.go +++ b/pkg/hicli/json-commands.go @@ -90,6 +90,14 @@ func (h *HiClient) handleJSONCommand(ctx context.Context, req *JSONCommand) (any return unmarshalAndCall(req.Data, func(params *getProfileParams) ([]id.RoomID, error) { return h.GetMutualRooms(ctx, params.UserID) }) + case "track_user_devices": + return unmarshalAndCall(req.Data, func(params *getProfileParams) (*ProfileEncryptionInfo, error) { + err := h.TrackUserDevices(ctx, params.UserID) + if err != nil { + return nil, err + } + return h.GetProfileEncryptionInfo(ctx, params.UserID) + }) case "get_profile_encryption_info": return unmarshalAndCall(req.Data, func(params *getProfileParams) (*ProfileEncryptionInfo, error) { return h.GetProfileEncryptionInfo(ctx, params.UserID) diff --git a/pkg/hicli/profile.go b/pkg/hicli/profile.go index 754ec6f..2b5bf2a 100644 --- a/pkg/hicli/profile.go +++ b/pkg/hicli/profile.go @@ -91,3 +91,8 @@ func (h *HiClient) GetProfileEncryptionInfo(ctx context.Context, userID id.UserI } return &resp, nil } + +func (h *HiClient) TrackUserDevices(ctx context.Context, userID id.UserID) error { + _, err := h.Crypto.FetchKeys(ctx, []id.UserID{userID}, true) + return err +} diff --git a/web/src/api/rpc.ts b/web/src/api/rpc.ts index 78c4c7b..98f3f8c 100644 --- a/web/src/api/rpc.ts +++ b/web/src/api/rpc.ts @@ -186,6 +186,10 @@ export default abstract class RPCClient { return this.request("get_profile_encryption_info", { user_id }) } + trackUserDevices(user_id: UserID): Promise { + return this.request("track_user_devices", { user_id }) + } + ensureGroupSessionShared(room_id: RoomID): Promise { return this.request("ensure_group_session_shared", { room_id }) } diff --git a/web/src/icons/devices-off.svg b/web/src/icons/devices-off.svg new file mode 100644 index 0000000..961aa6b --- /dev/null +++ b/web/src/icons/devices-off.svg @@ -0,0 +1 @@ + diff --git a/web/src/icons/devices.svg b/web/src/icons/devices.svg new file mode 100644 index 0000000..128d2d2 --- /dev/null +++ b/web/src/icons/devices.svg @@ -0,0 +1 @@ + diff --git a/web/src/ui/rightpanel/RightPanel.css b/web/src/ui/rightpanel/RightPanel.css index 43e8c17..76be03f 100644 --- a/web/src/ui/rightpanel/RightPanel.css +++ b/web/src/ui/rightpanel/RightPanel.css @@ -135,6 +135,13 @@ div.right-panel-content.user { display: inline-block; } + button.action { + padding: .5rem 1rem; + width: 100%; + gap: .25rem; + justify-content: left; + } + div.devices > details > ul { list-style-type: none; padding: 0; diff --git a/web/src/ui/rightpanel/UserInfo.tsx b/web/src/ui/rightpanel/UserInfo.tsx index e75ac97..e605278 100644 --- a/web/src/ui/rightpanel/UserInfo.tsx +++ b/web/src/ui/rightpanel/UserInfo.tsx @@ -73,7 +73,10 @@ const UserInfo = ({ userID }: UserInfoProps) => { }
- + {errors?.length ? <> + +
+ : null} } diff --git a/web/src/ui/rightpanel/UserInfoDeviceList.tsx b/web/src/ui/rightpanel/UserInfoDeviceList.tsx index 6e43b6b..0c668fc 100644 --- a/web/src/ui/rightpanel/UserInfoDeviceList.tsx +++ b/web/src/ui/rightpanel/UserInfoDeviceList.tsx @@ -13,12 +13,13 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import { useEffect, useState } from "react" +import { useCallback, useEffect, useState, useTransition } from "react" import { ScaleLoader } from "react-spinners" import Client from "@/api/client.ts" import { RoomStateStore } from "@/api/statestore" import { ProfileDevice, ProfileEncryptionInfo, TrustState, UserID } from "@/api/types" import UserInfoError from "./UserInfoError.tsx" +import DevicesIcon from "@/icons/devices.svg?react" import EncryptedOffIcon from "@/icons/encrypted-off.svg?react" import EncryptedQuestionIcon from "@/icons/encrypted-question.svg?react" import EncryptedIcon from "@/icons/encrypted.svg?react" @@ -32,6 +33,18 @@ interface DeviceListProps { const DeviceList = ({ client, room, userID }: DeviceListProps) => { const [view, setEncryptionInfo] = useState(null) const [errors, setErrors] = useState(null) + const [trackChangePending, startTransition] = useTransition() + const doTrackDeviceList = useCallback(() => { + startTransition(async () => { + try { + const resp = await client.rpc.trackUserDevices(userID) + setEncryptionInfo(resp) + setErrors(resp.errors) + } catch (err) { + setErrors([`${err}`]) + } + }) + }, [client, userID]) useEffect(() => { setEncryptionInfo(null) setErrors(null) @@ -60,6 +73,9 @@ const DeviceList = ({ client, room, userID }: DeviceListProps) => {

Security

{encryptionMessage}

This user's device list is not being tracked.

+ }