Skip to content

Commit 0beacd7

Browse files
committed
Enhance docs and types for secure storage
Add extensive JSDoc, examples and type refinements across the library. Introduces a SensitiveInfoApi facade, documents all core APIs (setItem/getItem/hasItem/deleteItem/getAllItems/clearService/getSupportedSecurityLevels/rotateKeys/getKeyVersion) with params, returns, errors and examples. Expands and documents typed error classes and predicates in errors.ts, improves hook-related types and helpers (HookError, AsyncState, mutation results) in hooks/types.ts, clarifies useSecureStorage usage docs, and enriches the Nitro interface and payload types in sensitive-info.nitro.ts (SecurityLevel, AccessControl, request/response shapes, StorageMetadata). Also updates package root docs and quick-start in index.ts. Changes are primarily documentation and typing improvements; no behavioral changes intended.
1 parent c6b16b5 commit 0beacd7

6 files changed

Lines changed: 678 additions & 42 deletions

File tree

src/core/storage.ts

Lines changed: 173 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,55 @@ const asTyped = (error: unknown): never => {
2525

2626
/**
2727
* Strongly typed façade around the underlying Nitro native object.
28+
*
29+
* Each member mirrors a top-level export: prefer the named exports for tree-shaking, or use this
30+
* interface when injecting/mocking the storage surface (e.g. in tests).
2831
*/
2932
export interface SensitiveInfoApi {
33+
/** See {@link setItem}. */
3034
readonly setItem: typeof setItem
35+
/** See {@link getItem}. */
3136
readonly getItem: typeof getItem
37+
/** See {@link hasItem}. */
3238
readonly hasItem: typeof hasItem
39+
/** See {@link deleteItem}. */
3340
readonly deleteItem: typeof deleteItem
41+
/** See {@link getAllItems}. */
3442
readonly getAllItems: typeof getAllItems
43+
/** See {@link clearService}. */
3544
readonly clearService: typeof clearService
45+
/** See {@link getSupportedSecurityLevels}. */
3646
readonly getSupportedSecurityLevels: typeof getSupportedSecurityLevels
47+
/** See {@link rotateKeys}. */
3748
readonly rotateKeys: typeof rotateKeys
49+
/** See {@link getKeyVersion}. */
3850
readonly getKeyVersion: typeof getKeyVersion
3951
}
4052

4153
/**
42-
* Persist a secret value in the platform secure storage.
54+
* Encrypts and persists a secret value in the platform secure storage.
55+
*
56+
* @param key - Stable identifier for the entry (non-empty). Re-using a key overwrites the prior value.
57+
* @param value - UTF-8 string to encrypt. Encode binary data as base64 before passing.
58+
* @param options - Storage policy. See {@link SensitiveInfoOptions} for defaults.
59+
* @returns The {@link MutationResult} containing the metadata that was actually applied (the
60+
* platform may downgrade the requested `accessControl`).
61+
*
62+
* @throws {@link AuthenticationCanceledError} if the user dismisses the biometric prompt.
63+
* @throws {@link KeyInvalidatedError} if the device's biometric set changed since the last write.
64+
* @throws {@link SensitiveInfoError} for any other native failure.
65+
*
66+
* @example
67+
* ```ts
68+
* await setItem('session-token', token, {
69+
* service: 'com.example.auth',
70+
* accessControl: 'secureEnclaveBiometry',
71+
* authenticationPrompt: { title: 'Save your session' },
72+
* })
73+
* ```
74+
*
75+
* @see {@link SensitiveInfoSetRequest}
76+
* @see {@link MutationResult}
4377
*/
4478
export async function setItem(
4579
key: string,
@@ -60,7 +94,30 @@ export async function setItem(
6094
}
6195

6296
/**
63-
* Retrieve a previously stored secret.
97+
* Retrieves a previously stored secret.
98+
*
99+
* @param key - Identifier of the entry to fetch.
100+
* @param options - Storage scoping plus an optional `includeValue` flag (defaults to `true`).
101+
* Pass `includeValue: false` to fetch metadata only — cheaper, and on iOS this avoids the
102+
* biometric prompt on auth-gated entries.
103+
* @returns The {@link SensitiveInfoItem}, or `null` when no entry exists for `key`.
104+
*
105+
* @remarks
106+
* `NotFoundError` is intentionally swallowed and surfaced as `null`. Every other native error is
107+
* mapped to its typed {@link SensitiveInfoError} subclass and re-thrown.
108+
*
109+
* @throws {@link AuthenticationCanceledError} if the user dismisses the biometric prompt.
110+
* @throws {@link IntegrityViolationError} if the on-disk record fails its HMAC check.
111+
* @throws {@link KeyInvalidatedError} if the entry's biometric guard is no longer satisfiable.
112+
*
113+
* @example
114+
* ```ts
115+
* const item = await getItem('session-token', { service: 'com.example.auth' })
116+
* if (item) console.log(item.value, item.metadata.securityLevel)
117+
* ```
118+
*
119+
* @see {@link SensitiveInfoGetRequest}
120+
* @see {@link SensitiveInfoItem}
64121
*/
65122
export async function getItem(
66123
key: string,
@@ -82,7 +139,23 @@ export async function getItem(
82139
}
83140

84141
/**
85-
* Determine whether a secret exists for the given key.
142+
* Cheap existence check that never decrypts the value.
143+
*
144+
* @param key - Identifier to look up.
145+
* @param options - Storage scoping. Avoid passing `accessControl` / `authenticationPrompt` here —
146+
* `hasItem` is designed to be silent and should not trigger biometrics.
147+
* @returns `true` when an entry exists for the key, `false` otherwise.
148+
*
149+
* @throws {@link SensitiveInfoError} for unexpected native failures (storage IO, etc.).
150+
*
151+
* @example
152+
* ```ts
153+
* if (await hasItem('session-token', { service: 'com.example.auth' })) {
154+
* // skip onboarding
155+
* }
156+
* ```
157+
*
158+
* @see {@link SensitiveInfoHasRequest}
86159
*/
87160
export async function hasItem(
88161
key: string,
@@ -101,7 +174,22 @@ export async function hasItem(
101174
}
102175

103176
/**
104-
* Delete a stored secret.
177+
* Deletes a stored secret. Idempotent — calling on a missing key resolves with `false` rather
178+
* than throwing.
179+
*
180+
* @param key - Identifier of the entry to delete.
181+
* @param options - Storage scoping. The `accessControl`/`authenticationPrompt` from the original
182+
* write are not required for deletion.
183+
* @returns `true` when an entry was removed, `false` when the key did not exist.
184+
*
185+
* @throws {@link SensitiveInfoError} for unexpected native failures.
186+
*
187+
* @example
188+
* ```ts
189+
* await deleteItem('session-token', { service: 'com.example.auth' })
190+
* ```
191+
*
192+
* @see {@link SensitiveInfoDeleteRequest}
105193
*/
106194
export async function deleteItem(
107195
key: string,
@@ -120,7 +208,22 @@ export async function deleteItem(
120208
}
121209

122210
/**
123-
* Enumerate all secrets stored under a service.
211+
* Enumerates every entry stored under the configured service namespace.
212+
*
213+
* @param options - Pass `{ includeValues: true }` to decrypt and return values; defaults to
214+
* metadata-only for performance and to avoid biometric prompts on protected entries.
215+
* @returns Array of {@link SensitiveInfoItem}. Returns `[]` when the service is empty.
216+
*
217+
* @throws {@link AuthenticationCanceledError} when `includeValues: true` and the user cancels.
218+
* @throws {@link SensitiveInfoError} for unexpected native failures.
219+
*
220+
* @example
221+
* ```ts
222+
* const items = await getAllItems({ service: 'com.example.auth' })
223+
* console.log(items.map((i) => i.key))
224+
* ```
225+
*
226+
* @see {@link SensitiveInfoEnumerateRequest}
124227
*/
125228
export async function getAllItems(
126229
options?: SensitiveInfoEnumerateRequest
@@ -138,7 +241,20 @@ export async function getAllItems(
138241
}
139242

140243
/**
141-
* Remove every secret associated with a service.
244+
* Removes every entry associated with a service.
245+
*
246+
* @param options - Storage scoping (only `service`/`keychainGroup`/`iosSynchronizable` matter).
247+
* @returns Resolves when the service has been emptied.
248+
*
249+
* @remarks This is **non-recoverable** — there is no undo. Prefer this over deleting keys one by
250+
* one when wiping a feature on logout.
251+
*
252+
* @throws {@link SensitiveInfoError} for unexpected native failures.
253+
*
254+
* @example
255+
* ```ts
256+
* await clearService({ service: 'com.example.auth' })
257+
* ```
142258
*/
143259
export async function clearService(
144260
options?: SensitiveInfoOptions
@@ -152,7 +268,23 @@ export async function clearService(
152268
}
153269

154270
/**
155-
* Inspect which security primitives are available on the current device.
271+
* Inspects which security primitives are available on the current device.
272+
*
273+
* Use this to drive feature flags (e.g. show "Enable Face ID" only when `biometry === true`)
274+
* before writing entries that require those capabilities.
275+
*
276+
* @returns A {@link SecurityAvailability} snapshot — `secureEnclave` is iOS-only, `strongBox` is
277+
* Android-only.
278+
*
279+
* @throws {@link SensitiveInfoError} for unexpected native failures.
280+
*
281+
* @example
282+
* ```ts
283+
* const caps = await getSupportedSecurityLevels()
284+
* if (caps.biometry) enableBiometricUnlock()
285+
* ```
286+
*
287+
* @see {@link useSecurityAvailability} for the React-hook variant.
156288
*/
157289
export async function getSupportedSecurityLevels(): Promise<SecurityAvailability> {
158290
const native = getNativeInstance()
@@ -164,7 +296,26 @@ export async function getSupportedSecurityLevels(): Promise<SecurityAvailability
164296
}
165297

166298
/**
167-
* Rotate the master key for a given service. Existing entries are re-encrypted lazily by default.
299+
* Rotates the master key for the given service.
300+
*
301+
* @param options - Pass `{ reEncryptEagerly: true }` to re-encrypt every existing entry in the
302+
* same call; defaults to lazy rotation (entries are migrated on next read/write).
303+
* @returns A {@link RotationResult} with `previousVersion`, `newVersion`, and `reEncryptedCount`.
304+
*
305+
* @remarks Eager rotation may trigger one biometric prompt **per protected entry** — prefer the
306+
* default lazy mode unless you need to migrate ciphertext immediately for compliance reasons.
307+
*
308+
* @throws {@link RotationFailedError} when the native layer cannot generate a new key.
309+
* @throws {@link AuthenticationCanceledError} during eager rotation when the user cancels.
310+
*
311+
* @example
312+
* ```ts
313+
* const { newVersion } = await rotateKeys({ service: 'com.example.auth' })
314+
* console.log(`rotated to v${newVersion}`)
315+
* ```
316+
*
317+
* @see {@link RotateKeysRequest}
318+
* @see {@link useKeyRotation}
168319
*/
169320
export async function rotateKeys(
170321
options?: RotateKeysRequest
@@ -183,6 +334,20 @@ export async function rotateKeys(
183334

184335
/**
185336
* Returns the currently active key version for the given service.
337+
*
338+
* @param options - Storage scoping. Avoid passing `authenticationPrompt` here — this call should
339+
* never trigger biometrics.
340+
* @returns A non-negative integer. `0` indicates a legacy entry that has not been rotated yet.
341+
*
342+
* @throws {@link SensitiveInfoError} for unexpected native failures.
343+
*
344+
* @example
345+
* ```ts
346+
* const version = await getKeyVersion({ service: 'com.example.auth' })
347+
* if (version === 0) await rotateKeys({ service: 'com.example.auth' })
348+
* ```
349+
*
350+
* @see {@link rotateKeys}
186351
*/
187352
export async function getKeyVersion(
188353
options?: SensitiveInfoOptions

0 commit comments

Comments
 (0)