1 import { useCallback, useEffect, useState } from 'react';
4 DEFAULT_MEMORABLE_PW_OPTIONS,
5 DEFAULT_RANDOM_PW_OPTIONS,
8 } from '@proton/pass/lib/password/constants';
9 import type { GeneratePasswordConfig, GeneratePasswordMode } from '@proton/pass/lib/password/generator';
10 import { generatePassword } from '@proton/pass/lib/password/generator';
11 import type { MaybeNull } from '@proton/pass/types';
12 import { merge } from '@proton/pass/utils/object/merge';
13 import debounce from '@proton/utils/debounce';
15 export enum CharType {
21 /* Designers mixed the colors of different ui-${type}
22 * sub-themes for the character colors.. */
23 export const charTypeToClassName = {
24 [CharType.Alphabetic]: '',
25 [CharType.Digit]: 'ui-violet pass-password-generator--char-digit',
26 [CharType.Special]: 'ui-teal pass-password-generator--char-special',
29 export const getTypeFromChar = (char: string) => {
30 if (alphabeticChars.includes(char)) return CharType.Alphabetic;
31 if (digitChars.includes(char)) return CharType.Digit;
33 return CharType.Special;
36 export const getCharsGroupedByColor = (password: string) => {
37 if (password.length === 0) return [];
39 const [head, ...chars] = Array.from(password);
40 const startType = getTypeFromChar(head);
44 (state, currentChar) => {
45 const currentElement = state[state.length - 1];
46 const previousType = currentElement.color;
47 const currentType = getTypeFromChar(currentChar);
49 return previousType !== currentType
50 ? [...state, { color: currentType, content: currentChar }]
51 : [...state.slice(0, -1), { color: previousType, content: currentElement.content + currentChar }];
53 [{ color: startType, content: head }]
55 .map(({ color, content }, index) => (
56 <span className={charTypeToClassName[color]} key={index}>
62 type UsePasswordGeneratorOptions = {
63 initial: MaybeNull<GeneratePasswordConfig>;
64 onConfigChange: (options: GeneratePasswordConfig) => void;
67 export const usePasswordGenerator = ({ initial, onConfigChange }: UsePasswordGeneratorOptions) => {
68 const [config, setConfig] = useState<GeneratePasswordConfig>(initial ?? DEFAULT_MEMORABLE_PW_OPTIONS);
69 const [password, setPassword] = useState(() => generatePassword(config));
70 const regeneratePassword = () => setPassword(generatePassword(config));
72 /** debounce the pw options dispatch in order to avoid swarming the
73 * store with updates when using the length slider */
74 const savePasswordOptions = useCallback(debounce(onConfigChange, 250), []);
76 const setPasswordOptions = <T extends GeneratePasswordConfig['type']>(
78 update?: Partial<Extract<GeneratePasswordConfig, { type: T }>['options']>
80 setConfig((options) => {
81 const newOptions = (() => {
82 if (update) return merge(options, { options: update });
83 if (type === 'memorable') return DEFAULT_MEMORABLE_PW_OPTIONS;
84 if (type === 'random') return DEFAULT_RANDOM_PW_OPTIONS;
88 savePasswordOptions(newOptions);
93 /* regenerate the password on each options change */
94 useEffect(() => regeneratePassword(), Object.values(config.options));
105 export type UsePasswordGeneratorResult<T extends GeneratePasswordMode = GeneratePasswordMode> = Omit<
106 ReturnType<typeof usePasswordGenerator>,
108 > & { config: GeneratePasswordConfig<T> };
110 export const isUsingRandomPassword = (
111 result: UsePasswordGeneratorResult
112 ): result is UsePasswordGeneratorResult<'random'> => result.config.type === 'random';
114 export const isUsingMemorablePassword = (
115 result: UsePasswordGeneratorResult
116 ): result is UsePasswordGeneratorResult<'memorable'> => result.config.type === 'memorable';