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';
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 = ({
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 });
46 const items = await linkMapGenerator.next();
47 return { resultMetadata: items.value || [] };
50 const getItemInfo = (item: ESLink): { ID: string; timepoint: ESTimepoint } => ({
52 timepoint: [item.createTime, item.order],
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);
62 esSearchParams: keyword ? { normalisedKeywords: normalizeKeyword(keyword) } : undefined,
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;
73 const getEventFromIDB = async (
74 previousEventsObject?: EventsObject
76 newEvents: ESEvent<ESLink>[];
77 shouldRefresh: boolean;
78 eventsToStore: EventsObject;
80 let eventsObject: EventsObject;
81 if (previousEventsObject) {
82 eventsObject = previousEventsObject;
84 const storedEventIDs = await readAllLastEvents(userID);
85 if (!storedEventIDs) {
86 throw new Error('No event stored');
88 eventsObject = storedEventIDs;
91 const initialShareEvent = await api<DriveEventsResult>(queryEvents(await shareId, eventsObject[await shareId]));
93 let keepSyncing = Boolean(initialShareEvent.More);
96 const newEvents: DriveEventsResult[] = [initialShareEvent];
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');
105 keepSyncing = Boolean(newEventToCheck.More);
106 if (newEventToCheck.EventID !== lastEventId) {
107 newEvents.push(newEventToCheck);
111 const resolvedShareId = await shareId;
113 const shouldRefresh = newEvents.some((event) => {
114 return hasBit(event.Refresh, 1);
117 let eventsToStore: EventsObject = {};
118 eventsToStore[await shareId] = newEvents[newEvents.length - 1].EventID;
121 newEvents: await Promise.all(
123 // Encrypted seach can search only in my files through
124 // events per share which do not include ContextShareID.
127 Events: event.Events.map((item) => ({
129 ContextShareID: resolvedShareId,
132 .map((event) => driveEventsResultToDriveEvents(event))
133 .map((events) => convertDriveEventsToSearchEvents(resolvedShareId, events, getLinkPrivateKey))
144 getTotalItems: (() => {
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;
156 getKeywords: (esSearchParams: ESDriveSearchParams) => esSearchParams.normalisedKeywords,