Skip to content

Commit a4df50d

Browse files
committed
Refactor hooks and storage modules for improved API
Replaces legacy hooks implementation with modular, strongly-typed hooks in the src/hooks directory. Introduces a new core storage API in src/core/storage.ts and updates usage in example/App.tsx. Removes deprecated files and reorganizes internal modules for better maintainability. Adds babel-plugin-react-compiler to dev dependencies.
1 parent 246c314 commit a4df50d

24 files changed

+1251
-1034
lines changed

babel.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
module.exports = {
2+
plugins: ['babel-plugin-react-compiler'],
23
presets: ['module:@react-native/babel-preset'],
34
}

example/App.tsx

Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@ import {
2222
import {
2323
useSecureStorage,
2424
useSecurityAvailability,
25-
useSecretItem,
2625
type AccessControl,
27-
type SensitiveInfoItem,
26+
getItem,
2827
} from 'react-native-sensitive-info';
2928

3029
const DEFAULT_SERVICE = 'demo-service';
@@ -207,41 +206,6 @@ function App(): React.JSX.Element {
207206
refetch: refetchCapabilities,
208207
} = useSecurityAvailability();
209208

210-
const {
211-
items,
212-
isLoading: itemsLoading,
213-
error: storageError,
214-
saveSecret: hookSaveSecret,
215-
removeSecret: hookRemoveSecret,
216-
clearAll: hookClearAll,
217-
refreshItems,
218-
} = useSecureStorage({
219-
service: normalizedService,
220-
includeValues,
221-
});
222-
223-
const isOptionAvailable = useCallback(
224-
(value: AccessControl) => {
225-
if (!capabilities) {
226-
return true;
227-
}
228-
229-
switch (value) {
230-
case 'secureEnclaveBiometry':
231-
return capabilities.secureEnclave || capabilities.strongBox;
232-
case 'biometryCurrentSet':
233-
case 'biometryAny':
234-
return capabilities.biometry;
235-
case 'devicePasscode':
236-
return capabilities.deviceCredential;
237-
case 'none':
238-
default:
239-
return true;
240-
}
241-
},
242-
[capabilities],
243-
);
244-
245209
const normalizedService = useMemo(() => {
246210
const trimmed = service.trim();
247211
return trimmed.length > 0 ? trimmed : DEFAULT_SERVICE;
@@ -277,6 +241,46 @@ function App(): React.JSX.Element {
277241
],
278242
);
279243

244+
const storageOptions = useMemo(
245+
() => ({
246+
...baseOptions,
247+
includeValues,
248+
}),
249+
[baseOptions, includeValues],
250+
);
251+
252+
const {
253+
items,
254+
isLoading: itemsLoading,
255+
error: storageError,
256+
saveSecret: hookSaveSecret,
257+
removeSecret: hookRemoveSecret,
258+
clearAll: hookClearAll,
259+
refreshItems,
260+
} = useSecureStorage(storageOptions);
261+
262+
const isOptionAvailable = useCallback(
263+
(value: AccessControl) => {
264+
if (!capabilities) {
265+
return true;
266+
}
267+
268+
switch (value) {
269+
case 'secureEnclaveBiometry':
270+
return capabilities.secureEnclave || capabilities.strongBox;
271+
case 'biometryCurrentSet':
272+
case 'biometryAny':
273+
return capabilities.biometry;
274+
case 'devicePasscode':
275+
return capabilities.deviceCredential;
276+
case 'none':
277+
default:
278+
return true;
279+
}
280+
},
281+
[capabilities],
282+
);
283+
280284
useEffect(() => {
281285
if (!capabilities) {
282286
return;
@@ -338,7 +342,7 @@ function App(): React.JSX.Element {
338342
const handleGetItem = useCallback(async () => {
339343
await execute(async () => {
340344
try {
341-
const item = await useSecretItem(keyName, {
345+
const item = await getItem(keyName, {
342346
...baseOptions,
343347
includeValue: includeValueOnGet,
344348
});

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"@semantic-release/git": "^10.0.1",
6060
"@types/jest": "^30.0.0",
6161
"@types/react": "19.2.x",
62+
"babel-plugin-react-compiler": "^1.0.0",
6263
"conventional-changelog-conventionalcommits": "^9.1.0",
6364
"eslint": "^9.38.0",
6465
"eslint-config-airbnb": "^19.0.4",

src/core/storage.ts

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import type {
2+
MutationResult,
3+
SecurityAvailability,
4+
SensitiveInfoDeleteRequest,
5+
SensitiveInfoEnumerateRequest,
6+
SensitiveInfoGetRequest,
7+
SensitiveInfoHasRequest,
8+
SensitiveInfoItem,
9+
SensitiveInfoOptions,
10+
SensitiveInfoSetRequest,
11+
} from '../sensitive-info.nitro'
12+
import getNativeInstance from '../internal/native'
13+
import { normalizeOptions } from '../internal/options'
14+
import { isNotFoundError } from '../internal/errors'
15+
16+
/**
17+
* Strongly typed façade around the underlying Nitro native object.
18+
* Each function handles payload normalization before delegating to native code.
19+
*/
20+
export interface SensitiveInfoApi {
21+
readonly setItem: typeof setItem
22+
readonly getItem: typeof getItem
23+
readonly hasItem: typeof hasItem
24+
readonly deleteItem: typeof deleteItem
25+
readonly getAllItems: typeof getAllItems
26+
readonly clearService: typeof clearService
27+
readonly getSupportedSecurityLevels: typeof getSupportedSecurityLevels
28+
}
29+
30+
/**
31+
* Persist a secret value in the platform secure storage.
32+
* When possible, the native side elevates the access control to hardware-backed storage such as Secure Enclave or StrongBox.
33+
*/
34+
export async function setItem(
35+
key: string,
36+
value: string,
37+
options?: SensitiveInfoOptions
38+
): Promise<MutationResult> {
39+
const native = getNativeInstance()
40+
const payload: SensitiveInfoSetRequest = {
41+
key,
42+
value,
43+
...normalizeOptions(options),
44+
}
45+
return native.setItem(payload)
46+
}
47+
48+
/**
49+
* Retrieve a previously stored secret. Pass `includeValue: false` to fetch metadata only.
50+
*
51+
* @example
52+
* ```ts
53+
* const token = await getItem('refreshToken', { service: 'com.example.session' })
54+
* ```
55+
*/
56+
export async function getItem(
57+
key: string,
58+
options?: SensitiveInfoOptions & { includeValue?: boolean }
59+
): Promise<SensitiveInfoItem | null> {
60+
const native = getNativeInstance()
61+
const payload: SensitiveInfoGetRequest = {
62+
key,
63+
includeValue: options?.includeValue ?? true,
64+
...normalizeOptions(options),
65+
}
66+
67+
try {
68+
return await native.getItem(payload)
69+
} catch (error) {
70+
if (isNotFoundError(error)) {
71+
return null
72+
}
73+
throw error
74+
}
75+
}
76+
77+
/**
78+
* Determine whether a secret exists for the given key.
79+
*
80+
* @example
81+
* ```ts
82+
* const hasLegacyToken = await hasItem('legacyToken', { service: 'legacy' })
83+
* ```
84+
*/
85+
export async function hasItem(
86+
key: string,
87+
options?: SensitiveInfoOptions
88+
): Promise<boolean> {
89+
const native = getNativeInstance()
90+
const payload: SensitiveInfoHasRequest = {
91+
key,
92+
...normalizeOptions(options),
93+
}
94+
return native.hasItem(payload)
95+
}
96+
97+
/**
98+
* Delete a stored secret.
99+
*
100+
* @example
101+
* ```ts
102+
* await deleteItem('refreshToken', { service: 'com.example.session' })
103+
* ```
104+
*/
105+
export async function deleteItem(
106+
key: string,
107+
options?: SensitiveInfoOptions
108+
): Promise<boolean> {
109+
const native = getNativeInstance()
110+
const payload: SensitiveInfoDeleteRequest = {
111+
key,
112+
...normalizeOptions(options),
113+
}
114+
return native.deleteItem(payload)
115+
}
116+
117+
/**
118+
* Enumerate all secrets stored under a service. Values are omitted unless `includeValues` is set.
119+
*
120+
* @example
121+
* ```ts
122+
* const sessions = await getAllItems({ service: 'com.example.session', includeValues: true })
123+
* ```
124+
*/
125+
export async function getAllItems(
126+
options?: SensitiveInfoEnumerateRequest
127+
): Promise<SensitiveInfoItem[]> {
128+
const native = getNativeInstance()
129+
const payload: SensitiveInfoEnumerateRequest = {
130+
includeValues: options?.includeValues ?? false,
131+
...normalizeOptions(options),
132+
}
133+
return native.getAllItems(payload)
134+
}
135+
136+
/**
137+
* Remove every secret associated with a service.
138+
*
139+
* @example
140+
* ```ts
141+
* await clearService({ service: 'com.example.session' })
142+
* ```
143+
*/
144+
export async function clearService(
145+
options?: SensitiveInfoOptions
146+
): Promise<void> {
147+
const native = getNativeInstance()
148+
return native.clearService(normalizeOptions(options))
149+
}
150+
151+
/**
152+
* Inspect which security primitives are available on the current device.
153+
*
154+
* @example
155+
* ```ts
156+
* const support = await getSupportedSecurityLevels()
157+
* ```
158+
*/
159+
export function getSupportedSecurityLevels(): Promise<SecurityAvailability> {
160+
const native = getNativeInstance()
161+
return native.getSupportedSecurityLevels()
162+
}
163+
164+
/**
165+
* Convenient namespace exposing the secure storage surface area. Aids tree-shaking when consumers
166+
* destructure the API.
167+
*/
168+
export const SensitiveInfo: SensitiveInfoApi = {
169+
setItem,
170+
getItem,
171+
hasItem,
172+
deleteItem,
173+
getAllItems,
174+
clearService,
175+
getSupportedSecurityLevels,
176+
}
177+
178+
export default SensitiveInfo

src/hook-utils.ts

Lines changed: 0 additions & 57 deletions
This file was deleted.

0 commit comments

Comments
 (0)