Update selected item color in Pass menu
[ProtonMail-WebClient.git] / packages / pass / lib / invites / invite.requests.ts
blob4613f85b3e20eaaa4952b12de778cca9070ff19c
1 import { MAX_BATCH_ADDRESS_REQUEST, MAX_BATCH_PER_REQUEST } from '@proton/pass/constants';
2 import { api } from '@proton/pass/lib/api/api';
3 import { PassErrorCode } from '@proton/pass/lib/api/errors';
4 import { getPublicKeysForEmail } from '@proton/pass/lib/auth/address';
5 import { PassCrypto } from '@proton/pass/lib/crypto';
6 import type { NewUserPendingInvite, PendingInvite } from '@proton/pass/types/data/invites';
7 import type {
8     InviteAcceptIntent,
9     InviteNewUserDTO,
10     InviteRecommendationsIntent,
11     InviteRejectIntent,
12     InviteRemoveIntent,
13     InviteResendIntent,
14     InviteUserDTO,
15     NewUserInvitePromoteIntent,
16     NewUserInviteRemoveIntent,
17 } from '@proton/pass/types/data/invites.dto';
18 import { prop } from '@proton/pass/utils/fp/lens';
19 import { getApiError } from '@proton/shared/lib/api/helpers/apiErrorHelper';
20 import chunk from '@proton/utils/chunk';
22 export type InviteData = { invites: PendingInvite[]; newUserInvites: NewUserPendingInvite[] };
24 export const loadInvites = async (shareId: string): Promise<InviteData> => {
25     const { Invites, NewUserInvites } = await api({
26         url: `pass/v1/share/${shareId}/invite`,
27         method: 'get',
28     });
30     return {
31         invites: Invites.map(
32             (invite): PendingInvite => ({
33                 inviteId: invite.InviteID,
34                 targetId: invite.TargetID,
35                 targetType: invite.TargetType,
36                 invitedEmail: invite.InvitedEmail,
37                 inviterEmail: invite.InviterEmail,
38                 remindersSent: invite.RemindersSent,
39                 createTime: invite.CreateTime,
40                 modifyTime: invite.ModifyTime,
41             })
42         ),
43         newUserInvites: NewUserInvites.map(
44             (invite): NewUserPendingInvite => ({
45                 newUserInviteId: invite.NewUserInviteID!,
46                 targetId: invite.TargetID!,
47                 targetType: invite.TargetType!,
48                 invitedEmail: invite.InvitedEmail!,
49                 inviterEmail: invite.InviterEmail!,
50                 createTime: invite.CreateTime!,
51                 signature: invite.Signature!,
52                 state: invite.State!,
53             })
54         ),
55     };
58 const limitExceededCatch =
59     <T>(catchFn: () => T) =>
60     (error: unknown) => {
61         const { code } = getApiError(error);
62         if (code === PassErrorCode.RESOURCE_LIMIT_EXCEEDED) throw error;
63         else return catchFn();
64     };
66 /** Returns the list of emails which could not be invited successfully  */
67 export const createUserInvites = async (shareId: string, users: InviteUserDTO[]): Promise<string[]> =>
68     (
69         await Promise.all(
70             chunk(users, MAX_BATCH_PER_REQUEST).map(async (batch) =>
71                 api({
72                     url: `pass/v1/share/${shareId}/invite/batch`,
73                     method: 'post',
74                     data: {
75                         Invites: await Promise.all(
76                             batch.map(({ email, role, publicKey }) =>
77                                 PassCrypto.createVaultInvite({ shareId, email, role, invitedPublicKey: publicKey })
78                             )
79                         ),
80                     },
81                 })
82                     .then<string[]>(() => [])
83                     .catch(limitExceededCatch(() => batch.map(prop('email'))))
84             )
85         )
86     ).flat();
88 export const createNewUserInvites = async (shareId: string, newUsers: InviteNewUserDTO[]) =>
89     (
90         await Promise.all(
91             chunk(newUsers, MAX_BATCH_PER_REQUEST).map(async (batch) =>
92                 api({
93                     url: `pass/v1/share/${shareId}/invite/new_user/batch`,
94                     method: 'post',
95                     data: {
96                         NewUserInvites: await Promise.all(
97                             batch.map(({ email, role }) =>
98                                 PassCrypto.createNewUserVaultInvite({ email, role, shareId })
99                             )
100                         ),
101                     },
102                 })
103                     .then<string[]>(() => [])
104                     .catch(limitExceededCatch(() => batch.map(prop('email'))))
105             )
106         )
107     ).flat();
109 export const promoteInvite = async ({
110     invitedPublicKey,
111     newUserInviteId,
112     shareId,
113 }: NewUserInvitePromoteIntent & { invitedPublicKey: string }) =>
114     api({
115         url: `pass/v1/share/${shareId}/invite/new_user/${newUserInviteId}/keys`,
116         method: 'post',
117         data: await PassCrypto.promoteInvite({ shareId, invitedPublicKey }),
118     });
120 export const resendInvite = async ({ shareId, inviteId }: InviteResendIntent) =>
121     api({
122         url: `pass/v1/share/${shareId}/invite/${inviteId}/reminder`,
123         method: 'post',
124     });
126 export const removeInvite = async ({ shareId, inviteId }: InviteRemoveIntent) =>
127     api({
128         url: `pass/v1/share/${shareId}/invite/${inviteId}`,
129         method: 'delete',
130     });
132 export const removeNewUserInvite = async ({ shareId, newUserInviteId }: NewUserInviteRemoveIntent) =>
133     api({
134         url: `pass/v1/share/${shareId}/invite/new_user/${newUserInviteId}`,
135         method: 'delete',
136     });
138 export const acceptInvite = async ({ inviteToken, inviterEmail, invitedAddressId, inviteKeys }: InviteAcceptIntent) => {
139     return (
140         await api({
141             url: `pass/v1/invite/${inviteToken}`,
142             method: 'post',
143             data: await PassCrypto.acceptVaultInvite({
144                 invitedAddressId,
145                 inviteKeys,
146                 inviterPublicKeys: await getPublicKeysForEmail(inviterEmail),
147             }),
148         })
149     ).Share!;
152 export const rejectInvite = async ({ inviteToken }: InviteRejectIntent) =>
153     api({
154         url: `pass/v1/invite/${inviteToken}`,
155         method: 'delete',
156     });
158 export const getInviteRecommendations = async (
159     { shareId, pageSize, since, startsWith }: InviteRecommendationsIntent,
160     signal?: AbortSignal
161 ) => {
162     return (
163         await api({
164             url: `pass/v1/share/${shareId}/invite/recommended_emails`,
165             params: {
166                 PlanPageSize: pageSize,
167                 StartsWith: startsWith?.toLowerCase(),
168                 ...(since ? { PlanSince: since } : {}),
169             },
170             method: 'get',
171             signal: signal,
172         })
173     ).Recommendation!;
176 /** Check if an address can be invited - returns allowed addresses */
177 export const checkInviteAddresses = async (shareId: string, emails: string[]) =>
178     (
179         await Promise.all(
180             chunk(emails, MAX_BATCH_ADDRESS_REQUEST).map(async (batch) => {
181                 try {
182                     const result: { Code: number; Emails: string[] } = await api({
183                         url: `pass/v1/share/${shareId}/invite/check_address`,
184                         method: 'post',
185                         data: { Emails: batch },
186                     });
187                     return result.Emails;
188                 } catch {
189                     return [];
190                 }
191             })
192         )
193     ).flat();