Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / applications / drive / src / app / store / _search / useESCallbacks.tsx
blobfd11df1a34b70ed64b99731722fce825d7c5bc42
1 import { useHistory } from 'react-router-dom';
3 import type { PrivateKeyReference } from '@proton/crypto';
4 import type { CachedItem, ESCallbacks, ESEvent, ESTimepoint, EventsObject } from '@proton/encrypted-search';
5 import { normalizeKeyword, readAllLastEvents, testKeywords } from '@proton/encrypted-search';
6 import { queryEvents, queryLatestEvents } from '@proton/shared/lib/api/drive/share';
7 import { hasBit } from '@proton/shared/lib/helpers/bitset';
8 import type { Api, User } from '@proton/shared/lib/interfaces';
9 import type { DriveEventsResult } from '@proton/shared/lib/interfaces/drive/events';
11 import { driveEventsResultToDriveEvents } from '../_api';
12 import { createLinkGenerator } from './indexing/createLinkGenerator';
13 import convertDriveEventsToSearchEvents from './indexing/processEvent';
14 import type { FetchShareMap } from './indexing/useFetchShareMap';
15 import type { ESDriveSearchParams, ESLink } from './types';
16 import { extractSearchParameters } from './utils';
18 interface Props {
19     api: Api;
20     user: User;
21     shareId: Promise<string>;
22     fetchShareMap: FetchShareMap;
23     getSharePrivateKey: (abortSignal: AbortSignal, shareId: string) => Promise<PrivateKeyReference>;
24     getLinkPrivateKey: (abortSignal: AbortSignal, shareId: string, linkId: string) => Promise<PrivateKeyReference>;
27 let linkMapGenerator: AsyncGenerator<ESLink[]>;
29 export const useESCallbacks = ({
30     api,
31     user,
32     shareId,
33     fetchShareMap,
34     getSharePrivateKey,
35     getLinkPrivateKey,
36 }: Props): ESCallbacks<ESLink, ESDriveSearchParams> => {
37     const history = useHistory();
39     const userID = user.ID;
40     const queryItemsMetadata = async (signal: AbortSignal) => {
41         if (!linkMapGenerator) {
42             const rootKey = await getSharePrivateKey(signal, await shareId);
43             linkMapGenerator = createLinkGenerator(await shareId, rootKey, { fetchShareMap });
44         }
46         const items = await linkMapGenerator.next();
47         return { resultMetadata: items.value || [] };
48     };
50     const getItemInfo = (item: ESLink): { ID: string; timepoint: ESTimepoint } => ({
51         ID: item.id,
52         timepoint: [item.createTime, item.order],
53     });
55     const searchKeywords = (keywords: string[], itemToSearch: CachedItem<ESLink, void>, hasApostrophe: boolean) =>
56         testKeywords(keywords, [itemToSearch.metadata.decryptedName], hasApostrophe);
58     const getSearchParams = () => {
59         const keyword = extractSearchParameters(history.location);
60         return {
61             isSearch: !!keyword,
62             esSearchParams: keyword ? { normalisedKeywords: normalizeKeyword(keyword) } : undefined,
63         };
64     };
66     const getPreviousEventID = async (): Promise<EventsObject> => {
67         const latestEvent = await api<{ EventID: string }>(queryLatestEvents(await shareId));
68         let eventsToStore: EventsObject = {};
69         eventsToStore[await shareId] = latestEvent.EventID;
70         return eventsToStore;
71     };
73     const getEventFromIDB = async (
74         previousEventsObject?: EventsObject
75     ): Promise<{
76         newEvents: ESEvent<ESLink>[];
77         shouldRefresh: boolean;
78         eventsToStore: EventsObject;
79     }> => {
80         let eventsObject: EventsObject;
81         if (previousEventsObject) {
82             eventsObject = previousEventsObject;
83         } else {
84             const storedEventIDs = await readAllLastEvents(userID);
85             if (!storedEventIDs) {
86                 throw new Error('No event stored');
87             }
88             eventsObject = storedEventIDs;
89         }
91         const initialShareEvent = await api<DriveEventsResult>(queryEvents(await shareId, eventsObject[await shareId]));
93         let keepSyncing = Boolean(initialShareEvent.More);
94         let index = 0;
96         const newEvents: DriveEventsResult[] = [initialShareEvent];
97         while (keepSyncing) {
98             const lastEventId = newEvents[index++].EventID;
100             const newEventToCheck = await api<DriveEventsResult>(queryEvents(await shareId, lastEventId));
101             if (!newEventToCheck || !newEventToCheck.EventID) {
102                 throw new Error('No event found');
103             }
105             keepSyncing = Boolean(newEventToCheck.More);
106             if (newEventToCheck.EventID !== lastEventId) {
107                 newEvents.push(newEventToCheck);
108             }
109         }
111         const resolvedShareId = await shareId;
113         const shouldRefresh = newEvents.some((event) => {
114             return hasBit(event.Refresh, 1);
115         });
117         let eventsToStore: EventsObject = {};
118         eventsToStore[await shareId] = newEvents[newEvents.length - 1].EventID;
120         return {
121             newEvents: await Promise.all(
122                 newEvents
123                     // Encrypted seach can search only in my files through
124                     // events per share which do not include ContextShareID.
125                     .map((event) => ({
126                         ...event,
127                         Events: event.Events.map((item) => ({
128                             ...item,
129                             ContextShareID: resolvedShareId,
130                         })),
131                     }))
132                     .map((event) => driveEventsResultToDriveEvents(event))
133                     .map((events) => convertDriveEventsToSearchEvents(resolvedShareId, events, getLinkPrivateKey))
134             ),
135             shouldRefresh,
136             eventsToStore,
137         };
138     };
140     return {
141         getItemInfo,
142         queryItemsMetadata,
143         searchKeywords,
144         getTotalItems: (() => {
145             let total: number;
146             return async () => {
147                 if (!total) {
148                     // The Total property counts all files and folders, including the root
149                     // folder which is neither indexed nor shown to users. For ES purposes
150                     // it should not be counted toward the total, therefore the -1
151                     total = (await fetchShareMap({ shareId: await shareId })).Total - 1;
152                 }
153                 return total;
154             };
155         })(),
156         getKeywords: (esSearchParams: ESDriveSearchParams) => esSearchParams.normalisedKeywords,
157         getSearchParams,
158         getPreviousEventID,
159         getEventFromIDB,
160     };