Merge branch 'MAILWEB-6067-improve-circular-dependencies-prevention' into 'main'
[ProtonMail-WebClient.git] / packages / drive-store / store / _devices / useDevicesListing.tsx
blob8ef7e7d6fa656366415f08afb55d84589acaa576
1 import { createContext, useContext, useEffect, useState } from 'react';
3 import { c } from 'ttag';
5 import { useNotifications } from '@proton/components';
6 import { useLoading } from '@proton/hooks';
8 import { sendErrorReport } from '../../utils/errorHandling';
9 import { EnrichedError } from '../../utils/errorHandling/EnrichedError';
10 import { useLink } from '../_links';
11 import { useVolumesState } from '../_volumes';
12 import type { Device } from './interface';
13 import useDevicesApi from './useDevicesApi';
15 export function useDevicesListingProvider() {
16     const devicesApi = useDevicesApi();
17     const { getLink } = useLink();
18     const volumesState = useVolumesState();
19     const [state, setState] = useState<Map<string, Device>>(new Map());
20     const [isLoading, withLoading] = useLoading();
21     const { createNotification } = useNotifications();
23     const loadDevices = (abortSignal: AbortSignal) =>
24         withLoading(async () => {
25             const devices = await devicesApi.loadDevices(abortSignal);
27             if (devices) {
28                 const devicesMap = new Map();
29                 let hasError = false;
31                 for (const key in devices) {
32                     const { volumeId, shareId, linkId, name } = devices[key];
34                     try {
35                         volumesState.setVolumeShareIds(volumeId, [shareId]);
37                         devices[key] = {
38                             ...devices[key],
39                             name: name || (await getLink(abortSignal, shareId, linkId)).name,
40                         };
42                         devicesMap.set(key, devices[key]);
43                     } catch (e) {
44                         hasError = true;
46                         // Send an error report for this
47                         sendErrorReport(
48                             new EnrichedError('Decrypting device failed', {
49                                 tags: {
50                                     volumeId,
51                                     shareId,
52                                     linkId,
53                                 },
54                                 extra: {
55                                     e,
56                                 },
57                             })
58                         );
59                     }
60                 }
62                 if (hasError) {
63                     createNotification({
64                         type: 'error',
65                         text: c('Error').t`Error decrypting a computer`,
66                     });
67                 }
69                 setState(devicesMap);
70             }
71         });
73     const getState = () => {
74         return [...state.values()];
75     };
77     const getDeviceByShareId = (shareId: string) => {
78         return getState().find((device) => {
79             return device.shareId === shareId;
80         });
81     };
83     const removeDevice = (deviceId: string) => {
84         const newState = new Map(state);
85         newState.delete(deviceId);
86         setState(newState);
87     };
89     const renameDevice = (deviceId: string, name: string) => {
90         const newState = new Map(state);
91         const device = newState.get(deviceId);
92         if (!device) {
93             return;
94         }
95         newState.set(deviceId, {
96             ...device,
97             name,
98         });
99         setState(newState);
100     };
102     return {
103         isLoading,
104         loadDevices,
105         cachedDevices: getState(),
106         getDeviceByShareId,
107         renameDevice,
108         removeDevice,
109     };
112 const LinksListingContext = createContext<{
113     isLoading: boolean;
114     cachedDevices: ReturnType<typeof useDevicesListingProvider>['cachedDevices'];
115     getDeviceByShareId: ReturnType<typeof useDevicesListingProvider>['getDeviceByShareId'];
116     removeCachedDevice: ReturnType<typeof useDevicesListingProvider>['removeDevice'];
117     renameCachedDevice: ReturnType<typeof useDevicesListingProvider>['renameDevice'];
118 } | null>(null);
120 export function DevicesListingProvider({ children }: { children: React.ReactNode }) {
121     const value = useDevicesListingProvider();
123     useEffect(() => {
124         const ac = new AbortController();
125         value.loadDevices(ac.signal).catch(sendErrorReport);
127         return () => {
128             ac.abort();
129         };
130     }, []);
132     return (
133         <LinksListingContext.Provider
134             value={{
135                 isLoading: value.isLoading,
136                 cachedDevices: value.cachedDevices,
137                 getDeviceByShareId: value.getDeviceByShareId,
138                 removeCachedDevice: value.removeDevice,
139                 renameCachedDevice: value.renameDevice,
140             }}
141         >
142             {children}
143         </LinksListingContext.Provider>
144     );
147 export default function useDevicesListing() {
148     const state = useContext(LinksListingContext);
149     if (!state) {
150         throw new Error('Trying to use uninitialized LinksListingProvider');
151     }
152     return state;