1 import type { Reducer } from 'redux';
3 import { type PassThemeOption } from '@proton/pass/components/Layout/Theme/types';
4 import { LockMode } from '@proton/pass/lib/auth/lock/types';
5 import type { GeneratePasswordConfig } from '@proton/pass/lib/password/generator';
6 import { toggleCriteria } from '@proton/pass/lib/settings/criteria';
16 } from '@proton/pass/store/actions';
17 import { passwordOptionsEdit } from '@proton/pass/store/actions/creators/password';
18 import type { MaybeNull, Unpack } from '@proton/pass/types';
25 } from '@proton/pass/types/worker/settings';
26 import { or } from '@proton/pass/utils/fp/predicates';
27 import { partialMerge } from '@proton/pass/utils/object/merge';
29 export type SettingsState = {
30 autofill: AutoFillSettings;
31 autosave: AutoSaveSettings;
32 autosuggest: AutoSuggestSettings;
34 createdItemsCount: number /* explicitly created, not including import */;
35 disallowedDomains: DomainCriterias;
36 extraPassword?: boolean;
37 loadDomainImages: boolean;
41 offlineEnabled?: boolean;
42 passkeys: PasskeySettings;
43 passwordOptions: MaybeNull<GeneratePasswordConfig>;
44 showUsernameField?: boolean;
45 theme?: PassThemeOption;
48 export const EXCLUDED_SETTINGS_KEYS = ['createdItemsCount', 'lockMode', 'extraPassword'] as const;
49 export type ExcludedProxiedSettingsKeys = Unpack<typeof EXCLUDED_SETTINGS_KEYS>;
51 /* proxied settings will also be copied on local
52 * storage in order to access them before the booting
53 * sequence (ie: if the user has been logged out) */
54 export type ProxiedSettings = Omit<SettingsState, ExcludedProxiedSettingsKeys>;
56 export const getInitialSettings = (): ProxiedSettings => ({
57 autofill: { identity: true, twofa: true },
58 autosave: { prompt: true, passwordSuggest: true },
59 autosuggest: { password: true, email: true, passwordCopy: false },
60 disallowedDomains: {},
61 loadDomainImages: true,
62 passkeys: { get: true, create: true },
63 passwordOptions: null,
64 showUsernameField: false,
65 /* Theme is set to undefined so we can prompt <ThemeOnboardingModal> for discovery.
66 * Once we decide to no longer display that modal (e.g theme feature is not considered new anymore)
67 * then change value to PassThemeOption.OS */
71 const getInitialState = (): SettingsState => ({
73 lockMode: LockMode.NONE,
75 ...getInitialSettings(),
78 const reducer: Reducer<SettingsState> = (state = getInitialState(), action) => {
79 if (passwordOptionsEdit.match(action)) return { ...state, passwordOptions: action.payload };
81 if (itemCreationSuccess.match(action)) {
82 return partialMerge(state, { createdItemsCount: state.createdItemsCount + 1 });
85 if (or(lockCreateSuccess.match, lockSync.match)(action)) {
86 return partialMerge(state, { lockMode: action.payload.lock.mode, lockTTL: action.payload.lock.ttl });
89 if (settingsEditSuccess.match(action)) {
90 const update = { ...state };
92 /* `disallowedDomains` update should act as a setter */
93 if ('disallowedDomains' in action.payload) update.disallowedDomains = {};
94 return partialMerge<SettingsState>(update, action.payload);
97 if (userEvent.match(action)) {
98 const locale = action.payload.UserSettings?.Locale;
99 return locale ? partialMerge(state, { locale }) : state;
102 if (updatePauseListItem.match(action)) {
103 const { hostname, criteria } = action.payload;
104 const criteriasSetting = state.disallowedDomains[hostname] ?? 0;
106 return partialMerge<SettingsState>(state, {
108 [hostname]: toggleCriteria(criteriasSetting, criteria),
113 if (offlineToggle.success.match(action)) {
114 return partialMerge<SettingsState>(state, { offlineEnabled: action.payload });
117 if (extraPasswordToggle.success.match(action)) {
118 return partialMerge<SettingsState>(state, { extraPassword: action.payload });
124 export default reducer;