Remove client-side isLoggedIn value
[ProtonMail-WebClient.git] / packages / wallet / store / slices / apiWalletsData.ts
blobb58a04aa3e2e643965512e2cf26de84fa93b5e4d
1 import { type ThunkDispatch, type UnknownAction, createAction, createSlice } from '@reduxjs/toolkit';
2 import compact from 'lodash/compact';
3 import { c } from 'ttag';
5 import {
6     type AddressKeysState,
7     type ModelState,
8     type UserKeysState,
9     getInitialModelState,
10     userKeysThunk,
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 ({
32     extraArgument,
33     dispatch,
34 }: {
35     extraArgument: WalletThunkArguments;
36     dispatch: ThunkDispatch<ApiWalletsDataState, ProtonThunkArguments, UnknownAction>;
37 }) => {
38     const { walletApi, notificationsManager } = extraArgument;
39     const walletClient = walletApi.clients().wallet;
41     const userKeys = await dispatch(userKeysThunk());
43     return walletClient
44         .getWallets()
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))
53                         .catch(() => []);
55                     const apiWalletData = {
56                         Wallet: Wallet,
57                         WalletKey: WalletKey,
58                         WalletSettings: WalletSettings,
59                         WalletAccounts: accounts,
60                     };
62                     return decryptWallet({ apiWalletData, userKeys });
63                 })
64             );
66             return compact(decryptedWallets);
67         })
68         .catch((error: any) => {
69             notificationsManager.createNotification({
70                 type: 'error',
71                 text: error?.error ?? c('Wallet').t`Could not fetch wallets data`,
72             });
74             throw error;
75         });
78 const modelThunk = createAsyncModelThunk<Model, ApiWalletsDataState, WalletThunkArguments>(
79     `${apiWalletsDataSliceName}/fetch`,
80     {
81         miss: async ({ extraArgument, dispatch }) => {
82             return fetchAndDecryptWallets({ extraArgument, dispatch });
83         },
84         previous: previousSelector(selectApiWalletsData),
85     }
88 const initialState = getInitialModelState<Model>();
90 // Wallet actions
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 }) => ({
97         payload,
98     })
100 export const walletUpdate = createAction(
101     'wallet/update',
102     (payload: { walletID: string; update: Partial<IWasmApiWalletData['Wallet']> }) => ({
103         payload,
104     })
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 }) => ({
111         payload,
112     })
115 // Wallet account actions
116 export const walletAccountCreation = createAction('wallet-account/create', (payload: WasmApiWalletAccount) => ({
117     payload,
118 }));
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> }) => ({
126         payload,
127     })
130 const slice = createSlice({
131     name: apiWalletsDataSliceName,
132     initialState,
133     reducers: {},
134     extraReducers: (builder) => {
135         handleAsyncModel(builder, modelThunk);
137         builder
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);
141                 }
142             })
143             .addCase(setWalletPassphrase, (state, action) => {
144                 if (state.value) {
145                     const walletIndex = state.value.findIndex((data) => data.Wallet.ID === action.payload.walletID);
146                     state.value[walletIndex].Wallet.Passphrase = action.payload.passphrase;
147                 }
148             })
149             .addCase(walletDeletion, (state, action) => {
150                 if (state.value) {
151                     const walletIndex = state.value.findIndex((data) => data.Wallet.ID === action.payload.walletID);
152                     state.value.splice(walletIndex, 1);
153                 }
154             })
155             .addCase(walletUpdate, (state, action) => {
156                 if (state.value) {
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,
162                     };
163                 }
164             })
165             .addCase(disableWalletShowRecovery, (state, action) => {
166                 if (state.value) {
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;
171                     }
172                 }
173             })
174             .addCase(walletAccountCreation, (state, action) => {
175                 if (state.value) {
176                     const walletIndex = state.value.findIndex((data) => data.Wallet.ID === action.payload.WalletID);
177                     state.value[walletIndex].WalletAccounts.push(action.payload);
178                 }
179             })
180             .addCase(walletAccountUpdate, (state, action) => {
181                 if (state.value) {
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
187                     );
189                     state.value[walletIndex].WalletAccounts[walletAccountIndex] = {
190                         ...state.value[walletIndex].WalletAccounts[walletAccountIndex],
191                         ...action.payload.update,
192                     };
193                 }
194             })
195             .addCase(walletAccountDeletion, (state, action) => {
196                 if (state.value) {
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
202                     );
204                     state.value[walletIndex].WalletAccounts.splice(walletAccountIndex, 1);
205                 }
206             });
207     },
210 export const apiWalletsDataReducer = { [apiWalletsDataSliceName]: slice.reducer };
211 export const apiWalletsDataThunk = modelThunk.thunk;