1 import { type ThunkDispatch, type UnknownAction, createAction, createSlice } from '@reduxjs/toolkit';
2 import compact from 'lodash/compact';
3 import { c } from 'ttag';
11 } from '@proton/account';
12 import type { WasmApiWalletAccount, WasmApiWalletSettings } from '@proton/andromeda';
13 import { type ProtonThunkArguments } from '@proton/redux-shared-store-types';
14 import { createAsyncModelThunk, handleAsyncModel, previousSelector } from '@proton/redux-utilities';
16 import type { IWasmApiWalletData } from '../../types';
17 import { decryptWallet } from '../../utils';
18 import type { WalletThunkArguments } from '../thunk';
20 export const apiWalletsDataSliceName = 'api_wallets_data' as const;
22 export interface ApiWalletsDataState extends UserKeysState, AddressKeysState {
23 [apiWalletsDataSliceName]: ModelState<IWasmApiWalletData[]>;
26 type SliceState = ApiWalletsDataState[typeof apiWalletsDataSliceName];
27 type Model = NonNullable<SliceState['value']>;
29 export const selectApiWalletsData = (state: ApiWalletsDataState) => state[apiWalletsDataSliceName];
31 const fetchAndDecryptWallets = async ({
35 extraArgument: WalletThunkArguments;
36 dispatch: ThunkDispatch<ApiWalletsDataState, ProtonThunkArguments, UnknownAction>;
38 const { walletApi, notificationsManager } = extraArgument;
39 const walletClient = walletApi.clients().wallet;
41 const userKeys = await dispatch(userKeysThunk());
45 .then(async (payload): Promise<IWasmApiWalletData[]> => {
46 const wallets = payload[0];
48 const decryptedWallets = await Promise.all(
49 wallets.map(async ({ Wallet, WalletKey, WalletSettings }) => {
50 const accounts: WasmApiWalletAccount[] = await walletClient
51 .getWalletAccounts(Wallet.ID)
52 .then((accounts) => accounts[0].map((accountPayload) => accountPayload.Data))
55 const apiWalletData = {
58 WalletSettings: WalletSettings,
59 WalletAccounts: accounts,
62 return decryptWallet({ apiWalletData, userKeys });
66 return compact(decryptedWallets);
68 .catch((error: any) => {
69 notificationsManager.createNotification({
71 text: error?.error ?? c('Wallet').t`Could not fetch wallets data`,
78 const modelThunk = createAsyncModelThunk<Model, ApiWalletsDataState, WalletThunkArguments>(
79 `${apiWalletsDataSliceName}/fetch`,
81 miss: async ({ extraArgument, dispatch }) => {
82 return fetchAndDecryptWallets({ extraArgument, dispatch });
84 previous: previousSelector(selectApiWalletsData),
88 const initialState = getInitialModelState<Model>();
91 export const setWallets = createAction('wallet/set', (payload: IWasmApiWalletData[]) => ({ payload }));
93 export const walletCreation = createAction('wallet/create', (payload: IWasmApiWalletData) => ({ payload }));
94 export const setWalletPassphrase = createAction(
95 'wallet/set-passphrase',
96 (payload: { walletID: string; passphrase: string }) => ({
100 export const walletUpdate = createAction(
102 (payload: { walletID: string; update: Partial<IWasmApiWalletData['Wallet']> }) => ({
106 export const walletDeletion = createAction('wallet/delete', (payload: { walletID: string }) => ({ payload }));
108 export const disableWalletShowRecovery = createAction(
109 'wallet disable show recovery',
110 (payload: { walletID: string }) => ({
115 // Wallet account actions
116 export const walletAccountCreation = createAction('wallet-account/create', (payload: WasmApiWalletAccount) => ({
119 export const walletAccountDeletion = createAction(
120 'wallet-account/delete',
121 (payload: { walletID: string; walletAccountID: string }) => ({ payload })
123 export const walletAccountUpdate = createAction(
124 'wallet-account/update',
125 (payload: { walletID: string; walletAccountID: string; update: Partial<WasmApiWalletAccount> }) => ({
130 const slice = createSlice({
131 name: apiWalletsDataSliceName,
134 extraReducers: (builder) => {
135 handleAsyncModel(builder, modelThunk);
138 .addCase(walletCreation, (state, action) => {
139 if (state.value && !state.value.some(({ Wallet: { ID } }) => ID === action.payload.Wallet.ID)) {
140 state.value.push(action.payload);
143 .addCase(setWalletPassphrase, (state, action) => {
145 const walletIndex = state.value.findIndex((data) => data.Wallet.ID === action.payload.walletID);
146 state.value[walletIndex].Wallet.Passphrase = action.payload.passphrase;
149 .addCase(walletDeletion, (state, action) => {
151 const walletIndex = state.value.findIndex((data) => data.Wallet.ID === action.payload.walletID);
152 state.value.splice(walletIndex, 1);
155 .addCase(walletUpdate, (state, action) => {
157 const walletIndex = state.value.findIndex((data) => data.Wallet.ID === action.payload.walletID);
159 state.value[walletIndex].Wallet = {
160 ...state.value[walletIndex].Wallet,
161 ...action.payload.update,
165 .addCase(disableWalletShowRecovery, (state, action) => {
167 const walletIndex = state.value.findIndex((data) => data.Wallet.ID === action.payload.walletID);
169 if (state.value[walletIndex].WalletSettings) {
170 (state.value[walletIndex].WalletSettings as WasmApiWalletSettings).ShowWalletRecovery = false;
174 .addCase(walletAccountCreation, (state, action) => {
176 const walletIndex = state.value.findIndex((data) => data.Wallet.ID === action.payload.WalletID);
177 state.value[walletIndex].WalletAccounts.push(action.payload);
180 .addCase(walletAccountUpdate, (state, action) => {
182 const walletIndex = state.value.findIndex((data) => data.Wallet.ID === action.payload.walletID);
183 const walletAtIndex = state.value[walletIndex];
185 const walletAccountIndex = walletAtIndex.WalletAccounts.findIndex(
186 (data) => data.ID === action.payload.walletAccountID
189 state.value[walletIndex].WalletAccounts[walletAccountIndex] = {
190 ...state.value[walletIndex].WalletAccounts[walletAccountIndex],
191 ...action.payload.update,
195 .addCase(walletAccountDeletion, (state, action) => {
197 const walletIndex = state.value.findIndex((data) => data.Wallet.ID === action.payload.walletID);
198 const walletAtIndex = state.value[walletIndex];
200 const walletAccountIndex = walletAtIndex.WalletAccounts.findIndex(
201 (data) => data.ID === action.payload.walletAccountID
204 state.value[walletIndex].WalletAccounts.splice(walletAccountIndex, 1);
210 export const apiWalletsDataReducer = { [apiWalletsDataSliceName]: slice.reducer };
211 export const apiWalletsDataThunk = modelThunk.thunk;