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';
10 InviteRecommendationsIntent,
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`,
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,
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!,
58 const limitExceededCatch =
59 <T>(catchFn: () => T) =>
61 const { code } = getApiError(error);
62 if (code === PassErrorCode.RESOURCE_LIMIT_EXCEEDED) throw error;
63 else return catchFn();
66 /** Returns the list of emails which could not be invited successfully */
67 export const createUserInvites = async (shareId: string, users: InviteUserDTO[]): Promise<string[]> =>
70 chunk(users, MAX_BATCH_PER_REQUEST).map(async (batch) =>
72 url: `pass/v1/share/${shareId}/invite/batch`,
75 Invites: await Promise.all(
76 batch.map(({ email, role, publicKey }) =>
77 PassCrypto.createVaultInvite({ shareId, email, role, invitedPublicKey: publicKey })
82 .then<string[]>(() => [])
83 .catch(limitExceededCatch(() => batch.map(prop('email'))))
88 export const createNewUserInvites = async (shareId: string, newUsers: InviteNewUserDTO[]) =>
91 chunk(newUsers, MAX_BATCH_PER_REQUEST).map(async (batch) =>
93 url: `pass/v1/share/${shareId}/invite/new_user/batch`,
96 NewUserInvites: await Promise.all(
97 batch.map(({ email, role }) =>
98 PassCrypto.createNewUserVaultInvite({ email, role, shareId })
103 .then<string[]>(() => [])
104 .catch(limitExceededCatch(() => batch.map(prop('email'))))
109 export const promoteInvite = async ({
113 }: NewUserInvitePromoteIntent & { invitedPublicKey: string }) =>
115 url: `pass/v1/share/${shareId}/invite/new_user/${newUserInviteId}/keys`,
117 data: await PassCrypto.promoteInvite({ shareId, invitedPublicKey }),
120 export const resendInvite = async ({ shareId, inviteId }: InviteResendIntent) =>
122 url: `pass/v1/share/${shareId}/invite/${inviteId}/reminder`,
126 export const removeInvite = async ({ shareId, inviteId }: InviteRemoveIntent) =>
128 url: `pass/v1/share/${shareId}/invite/${inviteId}`,
132 export const removeNewUserInvite = async ({ shareId, newUserInviteId }: NewUserInviteRemoveIntent) =>
134 url: `pass/v1/share/${shareId}/invite/new_user/${newUserInviteId}`,
138 export const acceptInvite = async ({ inviteToken, inviterEmail, invitedAddressId, inviteKeys }: InviteAcceptIntent) => {
141 url: `pass/v1/invite/${inviteToken}`,
143 data: await PassCrypto.acceptVaultInvite({
146 inviterPublicKeys: await getPublicKeysForEmail(inviterEmail),
152 export const rejectInvite = async ({ inviteToken }: InviteRejectIntent) =>
154 url: `pass/v1/invite/${inviteToken}`,
158 export const getInviteRecommendations = async (
159 { shareId, pageSize, since, startsWith }: InviteRecommendationsIntent,
164 url: `pass/v1/share/${shareId}/invite/recommended_emails`,
166 PlanPageSize: pageSize,
167 StartsWith: startsWith?.toLowerCase(),
168 ...(since ? { PlanSince: since } : {}),
176 /** Check if an address can be invited - returns allowed addresses */
177 export const checkInviteAddresses = async (shareId: string, emails: string[]) =>
180 chunk(emails, MAX_BATCH_ADDRESS_REQUEST).map(async (batch) => {
182 const result: { Code: number; Emails: string[] } = await api({
183 url: `pass/v1/share/${shareId}/invite/check_address`,
185 data: { Emails: batch },
187 return result.Emails;