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 {
30 from: RequireSome<Recipient, 'Address' | 'Name'>;
33 to: RequireSome<Recipient, 'Address' | 'Name'>[];
35 plainTextBody?: string;
36 sendPreferencesMap?: SimpleMap<SendPreferences>;
37 contactEmailsMap?: SimpleMap<ContactEmail>;
40 const useSendIcs = () => {
42 const getAddressKeys = useGetAddressKeys();
43 const getMailSettings = useGetMailSettings();
44 const getEncryptionPreferences = useGetEncryptionPreferences();
46 const send = useCallback(
56 sendPreferencesMap = {},
58 }: SendIcsParams) => {
63 throw new Error('Missing addressID');
65 const { publicKeys: allPublicKeys, privateKeys: allPrivateKeys } = splitKeys(
66 await getAddressKeys(addressID)
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}`,
74 const packets = await encryptAttachment(ics, inviteAttachment, false, publicKeys, privateKeys);
75 const concatenatedPackets = mergeUint8Arrays(
76 [packets.data, packets.keys, packets.signature].filter(isTruthy)
78 const emails = to.map(({ Address }) => Address);
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,
86 const attachmentData = {
90 const directMessage = {
97 MIMEType: MIME_TYPES.PLAINTEXT,
98 Attachments: [pick(attachment, ['Filename', 'MIMEType', 'Contents'])],
99 Flags: Sign ? MESSAGE_FLAGS.FLAG_SIGN : undefined,
101 const sendPrefsMap: SimpleMap<SendPreferences> = {};
103 emails.map(async (email) => {
104 const existingSendPreferences = sendPreferencesMap[email];
105 if (existingSendPreferences) {
106 sendPrefsMap[email] = existingSendPreferences;
109 const encryptionPreferences = await getEncryptionPreferences({
114 const sendPreferences = getSendPreferences(encryptionPreferences, directMessage);
115 sendPrefsMap[email] = sendPreferences;
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;
125 const packages = await generatePackages({
126 message: directMessage,
127 sendPreferencesMap: sendPrefsMap,
129 attachments: [attachment],
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),
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;
146 await api({ ...sendMessageDirect(payload), silence: true });
148 [api, getMailSettings, getAddressKeys, getEncryptionPreferences]
153 export default useSendIcs;