1 import type { Action } from 'redux';
2 import { all, put, takeEvery } from 'redux-saga/effects';
4 import { parseItemRevision } from '@proton/pass/lib/items/item.parser';
5 import { createAlias, createItem, createItemWithAlias } from '@proton/pass/lib/items/item.requests';
6 import { createTelemetryEvent } from '@proton/pass/lib/telemetry/event';
7 import { itemCreationFailure, itemCreationIntent, itemCreationSuccess } from '@proton/pass/store/actions';
8 import { aliasOptionsRequest } from '@proton/pass/store/actions/requests';
9 import { requestInvalidate } from '@proton/pass/store/request/actions';
10 import type { RootSagaOptions } from '@proton/pass/store/types';
11 import type { 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';
15 type ItemCreationAction = ReturnType<typeof itemCreationIntent>;
16 type ItemWithAliasCreationAction = ItemCreationAction & { payload: { type: 'login'; extraData: { withAlias: true } } };
18 const singleItemCreation = (action: Action): action is ItemCreationAction =>
19 itemCreationIntent.match(action) && (action.payload.type === 'login' ? !action.payload.extraData.withAlias : true);
21 const withAliasItemCreation = (action: Action): action is ItemWithAliasCreationAction =>
22 itemCreationIntent.match(action) && action.payload.type === 'login' && action.payload.extraData.withAlias;
24 function* singleItemCreationWorker({ onItemsUpdated, getTelemetry }: RootSagaOptions, action: ItemCreationAction) {
26 payload: createIntent,
27 meta: { callback: onItemCreationIntentProcessed },
29 const { shareId, optimisticId } = createIntent;
30 const isAlias = createIntent.type === 'alias';
31 const telemetry = getTelemetry();
34 const encryptedItem: ItemRevisionContentsResponse = yield isAlias
35 ? createAlias(createIntent)
36 : createItem(createIntent);
38 const item: ItemRevision = yield parseItemRevision(shareId, encryptedItem);
40 const itemCreationSuccessAction = itemCreationSuccess({ optimisticId, shareId, item });
41 yield put(itemCreationSuccessAction);
42 yield isAlias && put(requestInvalidate(aliasOptionsRequest(shareId))); /* reset alias options */
45 createTelemetryEvent(TelemetryEventName.ItemCreation, {}, { type: TelemetryItemType[item.data.type] })
48 if (item.data.type === 'login' && deobfuscate(item.data.content.totpUri)) {
49 void telemetry?.push(createTelemetryEvent(TelemetryEventName.TwoFACreation, {}, {}));
52 onItemCreationIntentProcessed?.(itemCreationSuccessAction);
55 const itemCreationFailureAction = itemCreationFailure({ optimisticId, shareId }, e);
56 yield put(itemCreationFailureAction);
58 onItemCreationIntentProcessed?.(itemCreationFailureAction);
62 function* withAliasCreationWorker(
63 { onItemsUpdated, getTelemetry }: RootSagaOptions,
64 { payload: createIntent }: ItemWithAliasCreationAction
66 const { shareId, optimisticId } = createIntent;
67 const telemetry = getTelemetry();
69 const [encryptedLoginItem, encryptedAliasItem]: ItemRevisionContentsResponse[] =
70 yield createItemWithAlias(createIntent);
72 const loginItem: ItemRevision = yield parseItemRevision(shareId, encryptedLoginItem);
73 const aliasItem: ItemRevision = yield parseItemRevision(shareId, encryptedAliasItem);
75 yield put(itemCreationSuccess({ optimisticId, shareId, item: loginItem, alias: aliasItem }));
76 yield put(requestInvalidate(aliasOptionsRequest(shareId))); /* reset alias options */
79 createTelemetryEvent(TelemetryEventName.ItemCreation, {}, { type: TelemetryItemType[loginItem.data.type] })
82 createTelemetryEvent(TelemetryEventName.ItemCreation, {}, { type: TelemetryItemType[aliasItem.data.type] })
84 if (loginItem.data.type === 'login' && deobfuscate(loginItem.data.content.totpUri)) {
85 void telemetry?.push(createTelemetryEvent(TelemetryEventName.TwoFACreation, {}, {}));
89 } catch (err: unknown) {
90 const itemCreationFailureAction = itemCreationFailure({ optimisticId, shareId }, err);
91 yield put(itemCreationFailureAction);
95 export default function* watcher(options: RootSagaOptions) {
97 takeEvery(singleItemCreation, singleItemCreationWorker, options),
98 takeEvery(withAliasItemCreation, withAliasCreationWorker, options),