@entropy-softworks/ui 2026.8.31 → 2026.8.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"password-generator.d.ts","sourceRoot":"","sources":["../../src/components/password-generator.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAoBxE,MAAM,WAAW,sBAAsB;IACrC,yFAAyF;IACzF,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,oDAkS5B,CAAC;AAEH,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"password-generator.d.ts","sourceRoot":"","sources":["../../src/components/password-generator.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAQ1B,MAAM,WAAW,sBAAsB;IACrC,yFAAyF;IACzF,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,oDAgM5B,CAAC;AAEH,eAAe,iBAAiB,CAAC"}
@@ -1,140 +1,19 @@
1
- import React, { useCallback, useEffect, useRef, useState } from "react";
2
- import { ActivityIndicator, Platform, Pressable, View } from "react-native";
1
+ import React from "react";
2
+ import { ActivityIndicator, Pressable, View } from "react-native";
3
3
  import { Ionicons } from "@expo/vector-icons";
4
4
  import { useColorScheme } from "nativewind";
5
- import * as Haptics from "expo-haptics";
6
5
  import Slider from "@react-native-community/slider";
7
- import Toast from "react-native-toast-message";
8
6
  import { Text } from "./text";
9
- import { copyToClipboard } from "../utils/clipboard";
10
- import { generatePassword } from "../utils/password";
11
- import { checkPasswordPwned } from "../services/hibp";
12
- // A randomly drawn password being in a breach corpus is astronomically
13
- // unlikely, but if it ever hits, regenerate until clean — capped so a
14
- // misbehaving API can't loop forever.
15
- const MAX_BREACH_REGEN = 5;
16
- const isWeb = Platform.OS === "web";
7
+ import { usePasswordGenerator } from "../hooks/usePasswordGenerator";
17
8
  /**
18
9
  * UI shell over `utils/password.generatePassword`. Owns length slider,
19
10
  * character-set toggles, copy-to-clipboard (via shared util with auto-
20
11
  * clear), and a coarse strength indicator.
21
12
  */
22
13
  export const PasswordGenerator = React.memo(function PasswordGenerator({ onSelect, hideCopy, }) {
23
- const [password, setPassword] = useState("");
24
- const [length, setLength] = useState(16);
25
- const [includeUppercase, setIncludeUppercase] = useState(true);
26
- const [includeLowercase, setIncludeLowercase] = useState(true);
27
- const [includeNumbers, setIncludeNumbers] = useState(true);
28
- const [includeSymbols, setIncludeSymbols] = useState(true);
29
- const [excludeSimilar, setExcludeSimilar] = useState(false);
30
- const [breach, setBreach] = useState("idle");
14
+ const { password, length, setLength, includeUppercase, setIncludeUppercase, includeLowercase, setIncludeLowercase, includeNumbers, setIncludeNumbers, includeSymbols, setIncludeSymbols, excludeSimilar, setExcludeSimilar, breach, strength: passwordStrength, handleGenerate, handleCopy, } = usePasswordGenerator();
31
15
  const { colorScheme } = useColorScheme();
32
16
  const isDark = colorScheme === "dark";
33
- // Latest options, read by the breach-regen path without re-subscribing the
34
- // check effect on every option change.
35
- const optsRef = useRef({
36
- length,
37
- includeUppercase,
38
- includeLowercase,
39
- includeNumbers,
40
- includeSymbols,
41
- excludeSimilar,
42
- });
43
- optsRef.current = {
44
- length,
45
- includeUppercase,
46
- includeLowercase,
47
- includeNumbers,
48
- includeSymbols,
49
- excludeSimilar,
50
- };
51
- // Ignore stale async HIBP results once a newer password supersedes them.
52
- const breachTokenRef = useRef(0);
53
- const breachAttemptRef = useRef(0);
54
- // 150 ms throttle on Generate. The CSPRNG draw itself is cheap
55
- // (synchronous bytes), but a held-down Enter key or a stuck
56
- // accessibility-keyboard repeat could fire many calls per second
57
- // and the rejection-sampling loop inside `generatePassword` does
58
- // a small amount of work per call. The throttle also gates the
59
- // haptic so the user isn't getting buzzed at keypress rate.
60
- const lastGenerateRef = useRef(0);
61
- const handleGenerate = useCallback(() => {
62
- const now = Date.now();
63
- if (now - lastGenerateRef.current < 150) {
64
- return;
65
- }
66
- lastGenerateRef.current = now;
67
- try {
68
- const next = generatePassword({
69
- length,
70
- includeUppercase,
71
- includeLowercase,
72
- includeNumbers,
73
- includeSymbols,
74
- excludeSimilar,
75
- });
76
- setPassword(next);
77
- if (!isWeb) {
78
- Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
79
- }
80
- }
81
- catch (e) {
82
- Toast.show({
83
- type: "error",
84
- text1: e instanceof Error ? e.message : "Please select at least one character type",
85
- visibilityTime: 3000,
86
- });
87
- }
88
- }, [length, includeUppercase, includeLowercase, includeNumbers, includeSymbols, excludeSimilar]);
89
- const handleCopy = useCallback(async () => {
90
- if (!password) {
91
- return;
92
- }
93
- const ok = await copyToClipboard(password);
94
- if (!ok) {
95
- return;
96
- }
97
- if (!isWeb) {
98
- Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
99
- }
100
- Toast.show({ type: "success", text1: "Copied to clipboard", visibilityTime: 2000 });
101
- }, [password]);
102
- // Generate on mount and when character-class options change.
103
- useEffect(() => {
104
- handleGenerate();
105
- }, [handleGenerate]);
106
- // Verify each generated password against HIBP (k-anonymity — only the SHA-1
107
- // prefix leaves the device). Debounced so spamming Generate only checks the
108
- // final password. On the (near-impossible) breach hit, silently regenerate;
109
- // a failed/offline check surfaces as "unverified" rather than blocking.
110
- useEffect(() => {
111
- if (!password) {
112
- setBreach("idle");
113
- return;
114
- }
115
- const token = ++breachTokenRef.current;
116
- setBreach("checking");
117
- const timer = setTimeout(async () => {
118
- const res = await checkPasswordPwned(password);
119
- if (token !== breachTokenRef.current) {
120
- return;
121
- } // superseded
122
- if (res.isPwned === true && breachAttemptRef.current < MAX_BREACH_REGEN) {
123
- breachAttemptRef.current += 1;
124
- try {
125
- setPassword(generatePassword(optsRef.current));
126
- }
127
- catch {
128
- setBreach("unverified");
129
- }
130
- return;
131
- }
132
- breachAttemptRef.current = 0;
133
- setBreach(res.isPwned === null ? "unverified" : "safe");
134
- }, 400);
135
- return () => clearTimeout(timer);
136
- }, [password]);
137
- const passwordStrength = useStrength(password);
138
17
  return (<>
139
18
  <View className="mb-4 rounded-lg border border-border dark:border-neutral-800 bg-card p-4 dark:bg-neutral-900/50">
140
19
  <View className="mb-3 flex-row items-center justify-between">
@@ -221,38 +100,6 @@ export const PasswordGenerator = React.memo(function PasswordGenerator({ onSelec
221
100
  </>);
222
101
  });
223
102
  export default PasswordGenerator;
224
- /** Coarse strength indicator — purely UX, server doesn't see it. */
225
- function useStrength(password) {
226
- if (!password) {
227
- return { label: "Generate a password", color: "gray" };
228
- }
229
- let strength = 0;
230
- if (password.length >= 12) {
231
- strength++;
232
- }
233
- if (password.length >= 16) {
234
- strength++;
235
- }
236
- if (/[a-z]/.test(password)) {
237
- strength++;
238
- }
239
- if (/[A-Z]/.test(password)) {
240
- strength++;
241
- }
242
- if (/[0-9]/.test(password)) {
243
- strength++;
244
- }
245
- if (/[^A-Za-z0-9]/.test(password)) {
246
- strength++;
247
- }
248
- if (strength <= 2) {
249
- return { label: "Weak", color: "#ef4444" };
250
- }
251
- if (strength <= 4) {
252
- return { label: "Medium", color: "#f59e0b" };
253
- }
254
- return { label: "Strong", color: "#22c55e" };
255
- }
256
103
  function CharsetCheckbox({ label, checked, onToggle, isDark }) {
257
104
  return (<Pressable onPress={onToggle} className="flex-row items-center gap-3" accessibilityRole="checkbox" accessibilityLabel={label} accessibilityState={{ checked }}>
258
105
  <View style={{
@@ -1 +1 @@
1
- {"version":3,"file":"password-generator.jsx","sourceRoot":"","sources":["../../src/components/password-generator.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"password-generator.jsx","sourceRoot":"","sources":["../../src/components/password-generator.tsx"],"names":[],"mappings":"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"}
@@ -3,4 +3,5 @@ export { useShakeAnimation } from "./useShakeAnimation";
3
3
  export { useDebouncedValue } from "./useDebouncedValue";
4
4
  export { useAutoLock, type UseAutoLockOptions } from "./useAutoLock";
5
5
  export { useUploadBlobUrl, type UploadBlobUrlResult } from "./useUploadBlobUrl";
6
+ export { usePasswordGenerator, getPasswordStrength, type UsePasswordGeneratorResult, type PasswordStrength, type BreachState, } from "./usePasswordGenerator";
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,OAAO,EACP,OAAO,EACP,cAAc,EACd,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,GAC7B,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAErE,OAAO,EAAE,gBAAgB,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,OAAO,EACP,OAAO,EACP,cAAc,EACd,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,GAC7B,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAErE,OAAO,EAAE,gBAAgB,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEhF,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,0BAA0B,EAC/B,KAAK,gBAAgB,EACrB,KAAK,WAAW,GACjB,MAAM,wBAAwB,CAAC"}
@@ -3,4 +3,5 @@ export { useShakeAnimation } from "./useShakeAnimation";
3
3
  export { useDebouncedValue } from "./useDebouncedValue";
4
4
  export { useAutoLock } from "./useAutoLock";
5
5
  export { useUploadBlobUrl } from "./useUploadBlobUrl";
6
+ export { usePasswordGenerator, getPasswordStrength, } from "./usePasswordGenerator";
6
7
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,OAAO,EACP,OAAO,EACP,cAAc,EACd,UAAU,GAKX,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAE,WAAW,EAA2B,MAAM,eAAe,CAAC;AAErE,OAAO,EAAE,gBAAgB,EAA4B,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,OAAO,EACP,OAAO,EACP,cAAc,EACd,UAAU,GAKX,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAE,WAAW,EAA2B,MAAM,eAAe,CAAC;AAErE,OAAO,EAAE,gBAAgB,EAA4B,MAAM,oBAAoB,CAAC;AAEhF,OAAO,EACL,oBAAoB,EACpB,mBAAmB,GAIpB,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { type Dispatch, type SetStateAction } from "react";
2
+ export type BreachState = "idle" | "checking" | "safe" | "unverified";
3
+ export interface PasswordStrength {
4
+ label: string;
5
+ color: string;
6
+ }
7
+ export interface UsePasswordGeneratorResult {
8
+ password: string;
9
+ length: number;
10
+ setLength: Dispatch<SetStateAction<number>>;
11
+ includeUppercase: boolean;
12
+ setIncludeUppercase: Dispatch<SetStateAction<boolean>>;
13
+ includeLowercase: boolean;
14
+ setIncludeLowercase: Dispatch<SetStateAction<boolean>>;
15
+ includeNumbers: boolean;
16
+ setIncludeNumbers: Dispatch<SetStateAction<boolean>>;
17
+ includeSymbols: boolean;
18
+ setIncludeSymbols: Dispatch<SetStateAction<boolean>>;
19
+ excludeSimilar: boolean;
20
+ setExcludeSimilar: Dispatch<SetStateAction<boolean>>;
21
+ breach: BreachState;
22
+ strength: PasswordStrength;
23
+ handleGenerate: () => void;
24
+ handleCopy: () => Promise<void>;
25
+ }
26
+ export declare function usePasswordGenerator(): UsePasswordGeneratorResult;
27
+ /** Coarse strength indicator — purely UX, server doesn't see it. */
28
+ export declare function getPasswordStrength(password: string): PasswordStrength;
29
+ //# sourceMappingURL=usePasswordGenerator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePasswordGenerator.d.ts","sourceRoot":"","sources":["../../src/hooks/usePasswordGenerator.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,cAAc,EAA4C,MAAM,OAAO,CAAC;AAYrG,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,YAAY,CAAC;AAItE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5C,gBAAgB,EAAE,OAAO,CAAC;IAC1B,mBAAmB,EAAE,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACvD,gBAAgB,EAAE,OAAO,CAAC;IAC1B,mBAAmB,EAAE,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACvD,cAAc,EAAE,OAAO,CAAC;IACxB,iBAAiB,EAAE,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,cAAc,EAAE,OAAO,CAAC;IACxB,iBAAiB,EAAE,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,cAAc,EAAE,OAAO,CAAC;IACxB,iBAAiB,EAAE,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED,wBAAgB,oBAAoB,IAAI,0BAA0B,CA0IjE;AAED,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CA8BtE"}
@@ -0,0 +1,187 @@
1
+ // Stateful core of PasswordGenerator (components/password-generator.tsx),
2
+ // extracted so a platform-specific presentational layer can consume the
3
+ // same state/logic without re-implementing generate/copy/breach-check.
4
+ // Native's own UI (the vault mobile app's own screen) needed a genuinely
5
+ // different, more native-feeling layout rather than the web card/checkbox
6
+ // layout — see components/password-generator.tsx's own doc comment for
7
+ // why that stayed a separate render rather than one component branching
8
+ // on Platform.OS internally. User-requested, 2026-08-17.
9
+ import { useCallback, useEffect, useRef, useState } from "react";
10
+ import { Platform } from "react-native";
11
+ import * as Haptics from "expo-haptics";
12
+ import Toast from "react-native-toast-message";
13
+ import { copyToClipboard } from "../utils/clipboard";
14
+ import { generatePassword } from "../utils/password";
15
+ import { checkPasswordPwned } from "../services/hibp";
16
+ // A randomly drawn password being in a breach corpus is astronomically
17
+ // unlikely, but if it ever hits, regenerate until clean — capped so a
18
+ // misbehaving API can't loop forever.
19
+ const MAX_BREACH_REGEN = 5;
20
+ const isWeb = Platform.OS === "web";
21
+ export function usePasswordGenerator() {
22
+ const [password, setPassword] = useState("");
23
+ const [length, setLength] = useState(16);
24
+ const [includeUppercase, setIncludeUppercase] = useState(true);
25
+ const [includeLowercase, setIncludeLowercase] = useState(true);
26
+ const [includeNumbers, setIncludeNumbers] = useState(true);
27
+ const [includeSymbols, setIncludeSymbols] = useState(true);
28
+ const [excludeSimilar, setExcludeSimilar] = useState(false);
29
+ const [breach, setBreach] = useState("idle");
30
+ // Latest options, read by the breach-regen path without re-subscribing
31
+ // the check effect on every option change.
32
+ const optsRef = useRef({
33
+ length,
34
+ includeUppercase,
35
+ includeLowercase,
36
+ includeNumbers,
37
+ includeSymbols,
38
+ excludeSimilar,
39
+ });
40
+ optsRef.current = {
41
+ length,
42
+ includeUppercase,
43
+ includeLowercase,
44
+ includeNumbers,
45
+ includeSymbols,
46
+ excludeSimilar,
47
+ };
48
+ // Ignore stale async HIBP results once a newer password supersedes them.
49
+ const breachTokenRef = useRef(0);
50
+ const breachAttemptRef = useRef(0);
51
+ // 150 ms throttle on Generate. The CSPRNG draw itself is cheap
52
+ // (synchronous bytes), but a held-down Enter key or a stuck
53
+ // accessibility-keyboard repeat could fire many calls per second
54
+ // and the rejection-sampling loop inside `generatePassword` does
55
+ // a small amount of work per call. The throttle also gates the
56
+ // haptic so the user isn't getting buzzed at keypress rate.
57
+ const lastGenerateRef = useRef(0);
58
+ const handleGenerate = useCallback(() => {
59
+ const now = Date.now();
60
+ if (now - lastGenerateRef.current < 150) {
61
+ return;
62
+ }
63
+ lastGenerateRef.current = now;
64
+ try {
65
+ const next = generatePassword({
66
+ length,
67
+ includeUppercase,
68
+ includeLowercase,
69
+ includeNumbers,
70
+ includeSymbols,
71
+ excludeSimilar,
72
+ });
73
+ setPassword(next);
74
+ if (!isWeb) {
75
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
76
+ }
77
+ }
78
+ catch (e) {
79
+ Toast.show({
80
+ type: "error",
81
+ text1: e instanceof Error ? e.message : "Please select at least one character type",
82
+ visibilityTime: 3000,
83
+ });
84
+ }
85
+ }, [length, includeUppercase, includeLowercase, includeNumbers, includeSymbols, excludeSimilar]);
86
+ const handleCopy = useCallback(async () => {
87
+ if (!password) {
88
+ return;
89
+ }
90
+ const ok = await copyToClipboard(password);
91
+ if (!ok) {
92
+ return;
93
+ }
94
+ if (!isWeb) {
95
+ Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
96
+ }
97
+ Toast.show({ type: "success", text1: "Copied to clipboard", visibilityTime: 2000 });
98
+ }, [password]);
99
+ // Generate on mount and when character-class options change.
100
+ useEffect(() => {
101
+ handleGenerate();
102
+ }, [handleGenerate]);
103
+ // Verify each generated password against HIBP (k-anonymity — only the SHA-1
104
+ // prefix leaves the device). Debounced so spamming Generate only checks the
105
+ // final password. On the (near-impossible) breach hit, silently regenerate;
106
+ // a failed/offline check surfaces as "unverified" rather than blocking.
107
+ useEffect(() => {
108
+ if (!password) {
109
+ setBreach("idle");
110
+ return;
111
+ }
112
+ const token = ++breachTokenRef.current;
113
+ setBreach("checking");
114
+ const timer = setTimeout(async () => {
115
+ const res = await checkPasswordPwned(password);
116
+ if (token !== breachTokenRef.current) {
117
+ return;
118
+ } // superseded
119
+ if (res.isPwned === true && breachAttemptRef.current < MAX_BREACH_REGEN) {
120
+ breachAttemptRef.current += 1;
121
+ try {
122
+ setPassword(generatePassword(optsRef.current));
123
+ }
124
+ catch {
125
+ setBreach("unverified");
126
+ }
127
+ return;
128
+ }
129
+ breachAttemptRef.current = 0;
130
+ setBreach(res.isPwned === null ? "unverified" : "safe");
131
+ }, 400);
132
+ return () => clearTimeout(timer);
133
+ }, [password]);
134
+ const strength = getPasswordStrength(password);
135
+ return {
136
+ password,
137
+ length,
138
+ setLength,
139
+ includeUppercase,
140
+ setIncludeUppercase,
141
+ includeLowercase,
142
+ setIncludeLowercase,
143
+ includeNumbers,
144
+ setIncludeNumbers,
145
+ includeSymbols,
146
+ setIncludeSymbols,
147
+ excludeSimilar,
148
+ setExcludeSimilar,
149
+ breach,
150
+ strength,
151
+ handleGenerate,
152
+ handleCopy,
153
+ };
154
+ }
155
+ /** Coarse strength indicator — purely UX, server doesn't see it. */
156
+ export function getPasswordStrength(password) {
157
+ if (!password) {
158
+ return { label: "Generate a password", color: "gray" };
159
+ }
160
+ let strength = 0;
161
+ if (password.length >= 12) {
162
+ strength++;
163
+ }
164
+ if (password.length >= 16) {
165
+ strength++;
166
+ }
167
+ if (/[a-z]/.test(password)) {
168
+ strength++;
169
+ }
170
+ if (/[A-Z]/.test(password)) {
171
+ strength++;
172
+ }
173
+ if (/[0-9]/.test(password)) {
174
+ strength++;
175
+ }
176
+ if (/[^A-Za-z0-9]/.test(password)) {
177
+ strength++;
178
+ }
179
+ if (strength <= 2) {
180
+ return { label: "Weak", color: "#ef4444" };
181
+ }
182
+ if (strength <= 4) {
183
+ return { label: "Medium", color: "#f59e0b" };
184
+ }
185
+ return { label: "Strong", color: "#22c55e" };
186
+ }
187
+ //# sourceMappingURL=usePasswordGenerator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePasswordGenerator.js","sourceRoot":"","sources":["../../src/hooks/usePasswordGenerator.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entropy-softworks/ui",
3
- "version": "2026.8.31",
3
+ "version": "2026.8.32",
4
4
  "private": false,
5
5
  "description": "Shared React Native UI primitives, auth screens, hooks, and services for Entropy Softworks Expo apps",
6
6
  "license": "MIT",
@@ -1,22 +1,10 @@
1
- import React, { useCallback, useEffect, useRef, useState } from "react";
2
- import { ActivityIndicator, Platform, Pressable, View } from "react-native";
1
+ import React from "react";
2
+ import { ActivityIndicator, Pressable, View } from "react-native";
3
3
  import { Ionicons } from "@expo/vector-icons";
4
4
  import { useColorScheme } from "nativewind";
5
- import * as Haptics from "expo-haptics";
6
5
  import Slider from "@react-native-community/slider";
7
- import Toast from "react-native-toast-message";
8
6
  import { Text } from "./text";
9
- import { copyToClipboard } from "../utils/clipboard";
10
- import { generatePassword } from "../utils/password";
11
- import { checkPasswordPwned } from "../services/hibp";
12
-
13
- // A randomly drawn password being in a breach corpus is astronomically
14
- // unlikely, but if it ever hits, regenerate until clean — capped so a
15
- // misbehaving API can't loop forever.
16
- const MAX_BREACH_REGEN = 5;
17
- type BreachState = "idle" | "checking" | "safe" | "unverified";
18
-
19
- const isWeb = Platform.OS === "web";
7
+ import { usePasswordGenerator } from "../hooks/usePasswordGenerator";
20
8
 
21
9
  export interface PasswordGeneratorProps {
22
10
  /** Called when the user taps "Use Password" (only shown when `onSelect` is provided). */
@@ -34,126 +22,28 @@ export const PasswordGenerator = React.memo(function PasswordGenerator({
34
22
  onSelect,
35
23
  hideCopy,
36
24
  }: PasswordGeneratorProps) {
37
- const [password, setPassword] = useState("");
38
- const [length, setLength] = useState(16);
39
- const [includeUppercase, setIncludeUppercase] = useState(true);
40
- const [includeLowercase, setIncludeLowercase] = useState(true);
41
- const [includeNumbers, setIncludeNumbers] = useState(true);
42
- const [includeSymbols, setIncludeSymbols] = useState(true);
43
- const [excludeSimilar, setExcludeSimilar] = useState(false);
44
- const [breach, setBreach] = useState<BreachState>("idle");
45
-
46
- const { colorScheme } = useColorScheme();
47
- const isDark = colorScheme === "dark";
48
-
49
- // Latest options, read by the breach-regen path without re-subscribing the
50
- // check effect on every option change.
51
- const optsRef = useRef({
52
- length,
53
- includeUppercase,
54
- includeLowercase,
55
- includeNumbers,
56
- includeSymbols,
57
- excludeSimilar,
58
- });
59
- optsRef.current = {
25
+ const {
26
+ password,
60
27
  length,
28
+ setLength,
61
29
  includeUppercase,
30
+ setIncludeUppercase,
62
31
  includeLowercase,
32
+ setIncludeLowercase,
63
33
  includeNumbers,
34
+ setIncludeNumbers,
64
35
  includeSymbols,
36
+ setIncludeSymbols,
65
37
  excludeSimilar,
66
- };
67
- // Ignore stale async HIBP results once a newer password supersedes them.
68
- const breachTokenRef = useRef(0);
69
- const breachAttemptRef = useRef(0);
70
-
71
- // 150 ms throttle on Generate. The CSPRNG draw itself is cheap
72
- // (synchronous bytes), but a held-down Enter key or a stuck
73
- // accessibility-keyboard repeat could fire many calls per second
74
- // and the rejection-sampling loop inside `generatePassword` does
75
- // a small amount of work per call. The throttle also gates the
76
- // haptic so the user isn't getting buzzed at keypress rate.
77
- const lastGenerateRef = useRef(0);
78
- const handleGenerate = useCallback(() => {
79
- const now = Date.now();
80
- if (now - lastGenerateRef.current < 150) {
81
- return;
82
- }
83
- lastGenerateRef.current = now;
84
- try {
85
- const next = generatePassword({
86
- length,
87
- includeUppercase,
88
- includeLowercase,
89
- includeNumbers,
90
- includeSymbols,
91
- excludeSimilar,
92
- });
93
- setPassword(next);
94
- if (!isWeb) {
95
- Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
96
- }
97
- } catch (e) {
98
- Toast.show({
99
- type: "error",
100
- text1: e instanceof Error ? e.message : "Please select at least one character type",
101
- visibilityTime: 3000,
102
- });
103
- }
104
- }, [length, includeUppercase, includeLowercase, includeNumbers, includeSymbols, excludeSimilar]);
105
-
106
- const handleCopy = useCallback(async () => {
107
- if (!password) {
108
- return;
109
- }
110
- const ok = await copyToClipboard(password);
111
- if (!ok) {
112
- return;
113
- }
114
- if (!isWeb) {
115
- Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
116
- }
117
- Toast.show({ type: "success", text1: "Copied to clipboard", visibilityTime: 2000 });
118
- }, [password]);
119
-
120
- // Generate on mount and when character-class options change.
121
- useEffect(() => {
122
- handleGenerate();
123
- }, [handleGenerate]);
38
+ setExcludeSimilar,
39
+ breach,
40
+ strength: passwordStrength,
41
+ handleGenerate,
42
+ handleCopy,
43
+ } = usePasswordGenerator();
124
44
 
125
- // Verify each generated password against HIBP (k-anonymity — only the SHA-1
126
- // prefix leaves the device). Debounced so spamming Generate only checks the
127
- // final password. On the (near-impossible) breach hit, silently regenerate;
128
- // a failed/offline check surfaces as "unverified" rather than blocking.
129
- useEffect(() => {
130
- if (!password) {
131
- setBreach("idle");
132
- return;
133
- }
134
- const token = ++breachTokenRef.current;
135
- setBreach("checking");
136
- const timer = setTimeout(async () => {
137
- const res = await checkPasswordPwned(password);
138
- if (token !== breachTokenRef.current) {
139
- return;
140
- } // superseded
141
- if (res.isPwned === true && breachAttemptRef.current < MAX_BREACH_REGEN) {
142
- breachAttemptRef.current += 1;
143
- try {
144
- setPassword(generatePassword(optsRef.current));
145
- } catch {
146
- setBreach("unverified");
147
- }
148
- return;
149
- }
150
- breachAttemptRef.current = 0;
151
- setBreach(res.isPwned === null ? "unverified" : "safe");
152
- }, 400);
153
- return () => clearTimeout(timer);
154
- }, [password]);
155
-
156
- const passwordStrength = useStrength(password);
45
+ const { colorScheme } = useColorScheme();
46
+ const isDark = colorScheme === "dark";
157
47
 
158
48
  return (
159
49
  <>
@@ -324,39 +214,6 @@ export const PasswordGenerator = React.memo(function PasswordGenerator({
324
214
 
325
215
  export default PasswordGenerator;
326
216
 
327
- /** Coarse strength indicator — purely UX, server doesn't see it. */
328
- function useStrength(password: string): { label: string; color: string } {
329
- if (!password) {
330
- return { label: "Generate a password", color: "gray" };
331
- }
332
- let strength = 0;
333
- if (password.length >= 12) {
334
- strength++;
335
- }
336
- if (password.length >= 16) {
337
- strength++;
338
- }
339
- if (/[a-z]/.test(password)) {
340
- strength++;
341
- }
342
- if (/[A-Z]/.test(password)) {
343
- strength++;
344
- }
345
- if (/[0-9]/.test(password)) {
346
- strength++;
347
- }
348
- if (/[^A-Za-z0-9]/.test(password)) {
349
- strength++;
350
- }
351
- if (strength <= 2) {
352
- return { label: "Weak", color: "#ef4444" };
353
- }
354
- if (strength <= 4) {
355
- return { label: "Medium", color: "#f59e0b" };
356
- }
357
- return { label: "Strong", color: "#22c55e" };
358
- }
359
-
360
217
  interface CharsetCheckboxProps {
361
218
  label: string;
362
219
  checked: boolean;
@@ -17,3 +17,11 @@ export { useDebouncedValue } from "./useDebouncedValue";
17
17
  export { useAutoLock, type UseAutoLockOptions } from "./useAutoLock";
18
18
 
19
19
  export { useUploadBlobUrl, type UploadBlobUrlResult } from "./useUploadBlobUrl";
20
+
21
+ export {
22
+ usePasswordGenerator,
23
+ getPasswordStrength,
24
+ type UsePasswordGeneratorResult,
25
+ type PasswordStrength,
26
+ type BreachState,
27
+ } from "./usePasswordGenerator";
@@ -0,0 +1,221 @@
1
+ // Stateful core of PasswordGenerator (components/password-generator.tsx),
2
+ // extracted so a platform-specific presentational layer can consume the
3
+ // same state/logic without re-implementing generate/copy/breach-check.
4
+ // Native's own UI (the vault mobile app's own screen) needed a genuinely
5
+ // different, more native-feeling layout rather than the web card/checkbox
6
+ // layout — see components/password-generator.tsx's own doc comment for
7
+ // why that stayed a separate render rather than one component branching
8
+ // on Platform.OS internally. User-requested, 2026-08-17.
9
+ import { type Dispatch, type SetStateAction, useCallback, useEffect, useRef, useState } from "react";
10
+ import { Platform } from "react-native";
11
+ import * as Haptics from "expo-haptics";
12
+ import Toast from "react-native-toast-message";
13
+ import { copyToClipboard } from "../utils/clipboard";
14
+ import { generatePassword } from "../utils/password";
15
+ import { checkPasswordPwned } from "../services/hibp";
16
+
17
+ // A randomly drawn password being in a breach corpus is astronomically
18
+ // unlikely, but if it ever hits, regenerate until clean — capped so a
19
+ // misbehaving API can't loop forever.
20
+ const MAX_BREACH_REGEN = 5;
21
+ export type BreachState = "idle" | "checking" | "safe" | "unverified";
22
+
23
+ const isWeb = Platform.OS === "web";
24
+
25
+ export interface PasswordStrength {
26
+ label: string;
27
+ color: string;
28
+ }
29
+
30
+ export interface UsePasswordGeneratorResult {
31
+ password: string;
32
+ length: number;
33
+ setLength: Dispatch<SetStateAction<number>>;
34
+ includeUppercase: boolean;
35
+ setIncludeUppercase: Dispatch<SetStateAction<boolean>>;
36
+ includeLowercase: boolean;
37
+ setIncludeLowercase: Dispatch<SetStateAction<boolean>>;
38
+ includeNumbers: boolean;
39
+ setIncludeNumbers: Dispatch<SetStateAction<boolean>>;
40
+ includeSymbols: boolean;
41
+ setIncludeSymbols: Dispatch<SetStateAction<boolean>>;
42
+ excludeSimilar: boolean;
43
+ setExcludeSimilar: Dispatch<SetStateAction<boolean>>;
44
+ breach: BreachState;
45
+ strength: PasswordStrength;
46
+ handleGenerate: () => void;
47
+ handleCopy: () => Promise<void>;
48
+ }
49
+
50
+ export function usePasswordGenerator(): UsePasswordGeneratorResult {
51
+ const [password, setPassword] = useState("");
52
+ const [length, setLength] = useState(16);
53
+ const [includeUppercase, setIncludeUppercase] = useState(true);
54
+ const [includeLowercase, setIncludeLowercase] = useState(true);
55
+ const [includeNumbers, setIncludeNumbers] = useState(true);
56
+ const [includeSymbols, setIncludeSymbols] = useState(true);
57
+ const [excludeSimilar, setExcludeSimilar] = useState(false);
58
+ const [breach, setBreach] = useState<BreachState>("idle");
59
+
60
+ // Latest options, read by the breach-regen path without re-subscribing
61
+ // the check effect on every option change.
62
+ const optsRef = useRef({
63
+ length,
64
+ includeUppercase,
65
+ includeLowercase,
66
+ includeNumbers,
67
+ includeSymbols,
68
+ excludeSimilar,
69
+ });
70
+ optsRef.current = {
71
+ length,
72
+ includeUppercase,
73
+ includeLowercase,
74
+ includeNumbers,
75
+ includeSymbols,
76
+ excludeSimilar,
77
+ };
78
+ // Ignore stale async HIBP results once a newer password supersedes them.
79
+ const breachTokenRef = useRef(0);
80
+ const breachAttemptRef = useRef(0);
81
+
82
+ // 150 ms throttle on Generate. The CSPRNG draw itself is cheap
83
+ // (synchronous bytes), but a held-down Enter key or a stuck
84
+ // accessibility-keyboard repeat could fire many calls per second
85
+ // and the rejection-sampling loop inside `generatePassword` does
86
+ // a small amount of work per call. The throttle also gates the
87
+ // haptic so the user isn't getting buzzed at keypress rate.
88
+ const lastGenerateRef = useRef(0);
89
+ const handleGenerate = useCallback(() => {
90
+ const now = Date.now();
91
+ if (now - lastGenerateRef.current < 150) {
92
+ return;
93
+ }
94
+ lastGenerateRef.current = now;
95
+ try {
96
+ const next = generatePassword({
97
+ length,
98
+ includeUppercase,
99
+ includeLowercase,
100
+ includeNumbers,
101
+ includeSymbols,
102
+ excludeSimilar,
103
+ });
104
+ setPassword(next);
105
+ if (!isWeb) {
106
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
107
+ }
108
+ } catch (e) {
109
+ Toast.show({
110
+ type: "error",
111
+ text1: e instanceof Error ? e.message : "Please select at least one character type",
112
+ visibilityTime: 3000,
113
+ });
114
+ }
115
+ }, [length, includeUppercase, includeLowercase, includeNumbers, includeSymbols, excludeSimilar]);
116
+
117
+ const handleCopy = useCallback(async () => {
118
+ if (!password) {
119
+ return;
120
+ }
121
+ const ok = await copyToClipboard(password);
122
+ if (!ok) {
123
+ return;
124
+ }
125
+ if (!isWeb) {
126
+ Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
127
+ }
128
+ Toast.show({ type: "success", text1: "Copied to clipboard", visibilityTime: 2000 });
129
+ }, [password]);
130
+
131
+ // Generate on mount and when character-class options change.
132
+ useEffect(() => {
133
+ handleGenerate();
134
+ }, [handleGenerate]);
135
+
136
+ // Verify each generated password against HIBP (k-anonymity — only the SHA-1
137
+ // prefix leaves the device). Debounced so spamming Generate only checks the
138
+ // final password. On the (near-impossible) breach hit, silently regenerate;
139
+ // a failed/offline check surfaces as "unverified" rather than blocking.
140
+ useEffect(() => {
141
+ if (!password) {
142
+ setBreach("idle");
143
+ return;
144
+ }
145
+ const token = ++breachTokenRef.current;
146
+ setBreach("checking");
147
+ const timer = setTimeout(async () => {
148
+ const res = await checkPasswordPwned(password);
149
+ if (token !== breachTokenRef.current) {
150
+ return;
151
+ } // superseded
152
+ if (res.isPwned === true && breachAttemptRef.current < MAX_BREACH_REGEN) {
153
+ breachAttemptRef.current += 1;
154
+ try {
155
+ setPassword(generatePassword(optsRef.current));
156
+ } catch {
157
+ setBreach("unverified");
158
+ }
159
+ return;
160
+ }
161
+ breachAttemptRef.current = 0;
162
+ setBreach(res.isPwned === null ? "unverified" : "safe");
163
+ }, 400);
164
+ return () => clearTimeout(timer);
165
+ }, [password]);
166
+
167
+ const strength = getPasswordStrength(password);
168
+
169
+ return {
170
+ password,
171
+ length,
172
+ setLength,
173
+ includeUppercase,
174
+ setIncludeUppercase,
175
+ includeLowercase,
176
+ setIncludeLowercase,
177
+ includeNumbers,
178
+ setIncludeNumbers,
179
+ includeSymbols,
180
+ setIncludeSymbols,
181
+ excludeSimilar,
182
+ setExcludeSimilar,
183
+ breach,
184
+ strength,
185
+ handleGenerate,
186
+ handleCopy,
187
+ };
188
+ }
189
+
190
+ /** Coarse strength indicator — purely UX, server doesn't see it. */
191
+ export function getPasswordStrength(password: string): PasswordStrength {
192
+ if (!password) {
193
+ return { label: "Generate a password", color: "gray" };
194
+ }
195
+ let strength = 0;
196
+ if (password.length >= 12) {
197
+ strength++;
198
+ }
199
+ if (password.length >= 16) {
200
+ strength++;
201
+ }
202
+ if (/[a-z]/.test(password)) {
203
+ strength++;
204
+ }
205
+ if (/[A-Z]/.test(password)) {
206
+ strength++;
207
+ }
208
+ if (/[0-9]/.test(password)) {
209
+ strength++;
210
+ }
211
+ if (/[^A-Za-z0-9]/.test(password)) {
212
+ strength++;
213
+ }
214
+ if (strength <= 2) {
215
+ return { label: "Weak", color: "#ef4444" };
216
+ }
217
+ if (strength <= 4) {
218
+ return { label: "Medium", color: "#f59e0b" };
219
+ }
220
+ return { label: "Strong", color: "#22c55e" };
221
+ }