Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / packages / components / hooks / useSendIcs.ts
blob49cf4bafd949e88aa682a867608e6859c7a38311
1 import { useCallback } from 'react';
3 import { useGetAddressKeys } from '@proton/account/addressKeys/hooks';
4 import useApi from '@proton/components/hooks/useApi';
5 import { useGetMailSettings } from '@proton/mail/mailSettings/hooks';
6 import { sendMessageDirect } from '@proton/shared/lib/api/messages';
7 import { ICAL_METHOD } from '@proton/shared/lib/calendar/constants';
8 import { MIME_TYPES } from '@proton/shared/lib/constants';
9 import { uint8ArrayToBase64String } from '@proton/shared/lib/helpers/encoding';
10 import { pick } from '@proton/shared/lib/helpers/object';
11 import type { Recipient } from '@proton/shared/lib/interfaces';
12 import type { ContactEmail } from '@proton/shared/lib/interfaces/contacts';
13 import type { SendPreferences } from '@proton/shared/lib/interfaces/mail/crypto';
14 import { SEND_MESSAGE_DIRECT_ACTION } from '@proton/shared/lib/interfaces/message';
15 import type { RequireSome, SimpleMap } from '@proton/shared/lib/interfaces/utils';
16 import { splitKeys } from '@proton/shared/lib/keys/keys';
17 import { MESSAGE_FLAGS } from '@proton/shared/lib/mail/constants';
18 import { AUTO_SAVE_CONTACTS } from '@proton/shared/lib/mail/mailSettings';
19 import { encryptAttachment } from '@proton/shared/lib/mail/send/attachments';
20 import generatePackages from '@proton/shared/lib/mail/send/generatePackages';
21 import getSendPreferences from '@proton/shared/lib/mail/send/getSendPreferences';
22 import isTruthy from '@proton/utils/isTruthy';
23 import mergeUint8Arrays from '@proton/utils/mergeUint8Arrays';
25 import useGetEncryptionPreferences from './useGetEncryptionPreferences';
27 export interface SendIcsParams {
28     method: ICAL_METHOD;
29     ics: string;
30     from: RequireSome<Recipient, 'Address' | 'Name'>;
31     addressID: string;
32     parentID?: string;
33     to: RequireSome<Recipient, 'Address' | 'Name'>[];
34     subject: string;
35     plainTextBody?: string;
36     sendPreferencesMap?: SimpleMap<SendPreferences>;
37     contactEmailsMap?: SimpleMap<ContactEmail>;
40 const useSendIcs = () => {
41     const api = useApi();
42     const getAddressKeys = useGetAddressKeys();
43     const getMailSettings = useGetMailSettings();
44     const getEncryptionPreferences = useGetEncryptionPreferences();
46     const send = useCallback(
47         async ({
48             method,
49             ics,
50             from,
51             addressID,
52             parentID,
53             to,
54             subject,
55             plainTextBody = '',
56             sendPreferencesMap = {},
57             contactEmailsMap,
58         }: SendIcsParams) => {
59             if (!to.length) {
60                 return;
61             }
62             if (!addressID) {
63                 throw new Error('Missing addressID');
64             }
65             const { publicKeys: allPublicKeys, privateKeys: allPrivateKeys } = splitKeys(
66                 await getAddressKeys(addressID)
67             );
68             const [publicKeys, privateKeys] = [allPublicKeys.slice(0, 1), allPrivateKeys.slice(0, 1)];
69             const { AutoSaveContacts, Sign } = await getMailSettings();
71             const inviteAttachment = new File([new Blob([ics])], 'invite.ics', {
72                 type: `text/calendar; method=${method}`,
73             });
74             const packets = await encryptAttachment(ics, inviteAttachment, false, publicKeys, privateKeys);
75             const concatenatedPackets = mergeUint8Arrays(
76                 [packets.data, packets.keys, packets.signature].filter(isTruthy)
77             );
78             const emails = to.map(({ Address }) => Address);
79             const attachment = {
80                 Filename: packets.Filename,
81                 MIMEType: packets.MIMEType,
82                 Contents: uint8ArrayToBase64String(concatenatedPackets),
83                 KeyPackets: uint8ArrayToBase64String(packets.keys),
84                 Signature: packets.signature ? uint8ArrayToBase64String(packets.signature) : undefined,
85             };
86             const attachmentData = {
87                 attachment,
88                 data: ics,
89             };
90             const directMessage = {
91                 ToList: to,
92                 CCList: [],
93                 BCCList: [],
94                 Subject: subject,
95                 Sender: from,
96                 Body: plainTextBody,
97                 MIMEType: MIME_TYPES.PLAINTEXT,
98                 Attachments: [pick(attachment, ['Filename', 'MIMEType', 'Contents'])],
99                 Flags: Sign ? MESSAGE_FLAGS.FLAG_SIGN : undefined,
100             };
101             const sendPrefsMap: SimpleMap<SendPreferences> = {};
102             await Promise.all(
103                 emails.map(async (email) => {
104                     const existingSendPreferences = sendPreferencesMap[email];
105                     if (existingSendPreferences) {
106                         sendPrefsMap[email] = existingSendPreferences;
107                         return;
108                     }
109                     const encryptionPreferences = await getEncryptionPreferences({
110                         email,
111                         lifetime: 0,
112                         contactEmailsMap,
113                     });
114                     const sendPreferences = getSendPreferences(encryptionPreferences, directMessage);
115                     sendPrefsMap[email] = sendPreferences;
116                 })
117             );
118             // throw if trying to send a reply to an organizer with send preferences error
119             if (method === ICAL_METHOD.REPLY) {
120                 const sendPrefError = sendPrefsMap[to[0].Address]?.error;
121                 if (sendPrefError) {
122                     throw sendPrefError;
123                 }
124             }
125             const packages = await generatePackages({
126                 message: directMessage,
127                 sendPreferencesMap: sendPrefsMap,
128                 attachmentData,
129                 attachments: [attachment],
130                 emails,
131                 publicKeys,
132                 privateKeys,
133             });
134             const payload: any = {
135                 Message: directMessage,
136                 AttachmentKeys: uint8ArrayToBase64String(packets.keys),
137                 // do not save organizer address as contact on REPLY (it could be a calendar group address)
138                 AutoSaveContacts: method === ICAL_METHOD.REPLY ? AUTO_SAVE_CONTACTS.DISABLED : AutoSaveContacts,
139                 Packages: Object.values(packages),
140             };
141             if (parentID) {
142                 // set the action to tell the API that this is a response to the message with ID = parentID
143                 payload.ParentID = parentID;
144                 payload.Action = SEND_MESSAGE_DIRECT_ACTION.REPLY;
145             }
146             await api({ ...sendMessageDirect(payload), silence: true });
147         },
148         [api, getMailSettings, getAddressKeys, getEncryptionPreferences]
149     );
150     return send;
153 export default useSendIcs;