1 import { call, put, select, takeEvery } from 'redux-saga/effects';
3 import { api } from '@proton/pass/lib/api/api';
4 import { parseItemRevision } from '@proton/pass/lib/items/item.parser';
5 import { editItem } from '@proton/pass/lib/items/item.requests';
6 import { createTelemetryEvent } from '@proton/pass/lib/telemetry/event';
7 import { aliasDetailsSync, itemEditFailure, itemEditIntent, itemEditSuccess } from '@proton/pass/store/actions';
8 import type { AliasState } from '@proton/pass/store/reducers';
9 import { selectAliasDetails, selectAliasOptions, selectItem } from '@proton/pass/store/selectors';
10 import type { RootSagaOptions } from '@proton/pass/store/types';
11 import type { ItemEditIntent, ItemRevision, ItemRevisionContentsResponse } from '@proton/pass/types';
12 import { TelemetryEventName, TelemetryItemType } from '@proton/pass/types/data/telemetry';
13 import { deobfuscate } from '@proton/pass/utils/obfuscate/xor';
14 import { isEqual } from '@proton/pass/utils/set/is-equal';
16 function* editMailboxesWorker(aliasEditIntent: ItemEditIntent<'alias'>) {
17 if (!aliasEditIntent.extraData) return;
19 const { itemId, shareId } = aliasEditIntent;
21 const item: ItemRevision<'alias'> = yield select(selectItem(shareId, itemId));
22 const mailboxesForAlias: string[] = yield select(selectAliasDetails(item.aliasEmail!));
23 const aliasOptions: AliasState['aliasOptions'] = yield select(selectAliasOptions);
25 const currentMailboxIds = new Set(
27 .map((mailbox) => aliasOptions?.mailboxes.find(({ email }) => email === mailbox)?.id)
28 .filter(Boolean) as number[]
31 const nextMailboxIds = new Set(aliasEditIntent.extraData.mailboxes.map(({ id }) => id));
33 /* only update the mailboxes if there is a change */
34 if (!isEqual(currentMailboxIds, nextMailboxIds)) {
36 url: `pass/v1/share/${shareId}/alias/${itemId}/mailbox`,
39 MailboxIDs: Array.from(nextMailboxIds.values()),
45 aliasEmail: item.aliasEmail!,
46 mailboxes: aliasEditIntent.extraData.mailboxes,
51 function* itemEditWorker(
52 { onItemsUpdated, getTelemetry }: RootSagaOptions,
53 { payload: editIntent, meta: { callback: onItemEditIntentProcessed } }: ReturnType<typeof itemEditIntent>
55 const { itemId, shareId, lastRevision } = editIntent;
56 const telemetry = getTelemetry();
59 if (editIntent.type === 'alias' && editIntent.extraData?.aliasOwner) {
60 yield call(editMailboxesWorker, editIntent);
63 const encryptedItem: ItemRevisionContentsResponse = yield editItem(editIntent, lastRevision);
64 const item: ItemRevision = yield parseItemRevision(shareId, encryptedItem);
66 const itemEditSuccessAction = itemEditSuccess({ item, itemId, shareId });
67 yield put(itemEditSuccessAction);
70 createTelemetryEvent(TelemetryEventName.ItemUpdate, {}, { type: TelemetryItemType[item.data.type] })
73 if (item.data.type === 'login' && editIntent.type === 'login') {
74 const prevTotp = deobfuscate(editIntent.content.totpUri);
75 const nextTotp = deobfuscate(item.data.content.totpUri);
77 if (nextTotp && prevTotp !== nextTotp) {
78 void telemetry?.push(createTelemetryEvent(TelemetryEventName.TwoFAUpdate, {}, {}));
82 onItemEditIntentProcessed?.(itemEditSuccessAction);
85 const itemEditFailureAction = itemEditFailure({ itemId, shareId }, e);
86 yield put(itemEditFailureAction);
88 onItemEditIntentProcessed?.(itemEditFailureAction);
92 export default function* watcher(options: RootSagaOptions) {
93 yield takeEvery(itemEditIntent.match, itemEditWorker, options);