Releases: mCodex/react-native-sensitive-info
v6.1.4
🔐 react-native-sensitive-info v6.1.4
🐛 iOS Biometric Prompt Fix
This release fixes duplicate Face ID / Touch ID prompts on iOS when working with biometric-protected Keychain entries.
✨ What Changed
- 🧠 Prompted value reads now own an
LAContextfrom the firstSecItemCopyMatchingcall. - 🤫
hasItemnow uses a dedicated silent existence path on iOS. - 🔎 Metadata-only reads and listings avoid triggering biometric UI.
- 🧩 JS option normalization is now operation-aware, so silent probes do not accidentally forward biometric prompt fields.
- 🪝 Hooks now avoid leaking prompt-bearing options into silent fetch/existence checks.
✅ Fixed Scenarios
- Calling
hasItem()beforegetItem()no longer causes two Face ID prompts. useHasSecret()remains silent even when callers reuse biometric option bags.useSecureStorage()metadata listings stay prompt-free unless values are explicitly requested.getItem(..., { includeValue: false })avoids biometric prompts for metadata-only reads.- Prompted
getItem()reads avoid retry-driven duplicate Face ID / Touch ID sheets.
🚀 Why This Matters
On iOS, even Keychain existence or attribute queries can require user presence for biometric-protected items. Previously, an app flow like:
await hasItem('session-token', { service: 'auth' })
await getItem('session-token', {
service: 'auth',
authenticationPrompt: { title: 'Unlock session' },
})could show Face ID twice: once during the existence check and once during the actual unlock.
Now, silent probes stay silent, and authentication is reserved for the operation that actually opens the protected value.
📱 Recommended iOS Validation
For apps using biometric-protected secrets, validate on a real iOS device:
- Store an item with biometric access control.
- Call
hasItem()during initialization. - Call
getItem()when the user explicitly unlocks. - Confirm there is no Face ID prompt during initialization and exactly one prompt during unlock.
🙌 Upgrade Notes
No public API changes are required. Existing apps should benefit automatically, especially flows that perform existence checks before reading a protected value.
v6.1.3
🛠️ v6.1.3 — Release Pipeline & Bundler Compatibility Fixes
TL;DR —
v6.1.2shipped a broken tarball: it was missing allnitrogen/generated/native bindings (breakingpod installwithcannot load such file -- nitrogen/generated/ios/SensitiveInfo+autolinking.rb) and itsexportsmap could not be resolved by some bundlers (Re.Pack/rspack), causingCannot find module 'react-native-sensitive-info/hooks'at build time.
v6.1.3is a pure release-tooling fix — no runtime/API changes. Upgrade and rebuild.
🚑 Bug Fixes
📦 Restore missing iOS / Android native bindings in the published tarball
v6.1.2 was published without any nitrogen/generated/ output because:
nitrogen/is gitignored (it's a build artifact).- The previous
release-itbefore:inithook only rantypecheck+bob build, notnitrogencodegen. - So when CI checked out a clean tree, the
nitrogen/directory never existed at publish time.
Symptom on consumer side:
[!] Invalid Podfile file: [!] Invalid SensitiveInfo.podspec file:
cannot load such file -- nitrogen/generated/ios/SensitiveInfo+autolinking.rb.
Fix: the release pipeline now always regenerates nitrogen artifacts (via npm run codegen) before packing/publishing, and aborts the release if any expected artifact is missing.
🔌 Fix Cannot find module 'react-native-sensitive-info/hooks' under Re.Pack / rspack
Some bundlers (notably Re.Pack/rspack, and certain Metro configurations) don't resolve the package exports map for subpath imports, so import { useSecret } from 'react-native-sensitive-info/hooks' failed at build time even though the files were physically present in node_modules.
Fix: added classic subpath proxy directories (hooks/, errors/) following the same pattern used by date-fns, lodash, react-router, and @reduxjs/toolkit. They expose main / module / react-native / types fields pointing into lib/..., so any bundler that falls back to filesystem resolution can find them. Modern bundlers continue to use the exports map — both paths resolve to the same files.
// hooks/package.json (shipped in the tarball)
{
"main": "../lib/commonjs/hooks/index.js",
"module": "../lib/module/hooks/index.js",
"react-native": "../lib/module/hooks/index.js",
"types": "../lib/typescript/commonjs/src/hooks/index.d.ts",
"sideEffects": false
}✅ Release Pipeline Hardening
To make sure this kind of regression can never ship again, release-it's before:init hook now runs a single release:prepare script that performs three sequential checks. Any failure aborts the publish + git push.
| # | Step | What it guarantees |
|---|---|---|
| 1 | 🏗️ npm run codegen |
Regenerates nitrogen/ (iOS/Android autolinking, shared C++ headers) and runs bob build. |
| 2 | 🔍 verify-release-artifacts.js |
Fast filesystem check for 11 critical artifacts in the workspace. Fails fast with an actionable message. |
| 3 | 🚀 smoke-test-release.js |
Full end-to-end consumer simulation: npm pack → install into a sandbox → resolve every documented subpath via both the exports map and the legacy proxy fields → ruby -c on the podspec + autolinking.rb to catch parse errors. |
If v6.1.2 had been gated by this pipeline, the release would have been blocked at step 3 with a clear error pointing at the missing nitrogen/generated/ios/SensitiveInfo+autolinking.rb.
🧰 What you need to do
Just bump the version — no code changes are required:
npm install react-native-sensitive-info@^6.1.3
# then on iOS:
cd ios && pod installIf you were stuck on v6.1.2 due to the pod install or Cannot find module 'react-native-sensitive-info/hooks' errors, both are resolved.
📁 Files changed
package.json— addedrelease:preparescript;before:initnow runs it;hooks/anderrors/added tofiles.hooks/package.json(new) — subpath proxy for bundlers withoutexportssupport.errors/package.json(new) — subpath proxy for bundlers withoutexportssupport.scripts/verify-release-artifacts.js(new) — workspace-level artifact check.scripts/smoke-test-release.js(new) — end-to-end pack/install/resolve smoke test.
v6.1.2
🚀 v6.1.2
A focused patch release that unblocks Re.Pack / rspack consumers, hardens the new cause property to fully match native ES2022 Error semantics, and ships a few CI hygiene fixes. No breaking changes — drop-in upgrade. 🎉
🐛 Bug Fixes
📦 Re.Pack / rspack: Cannot find module 'react-native-sensitive-info/hooks' (and /errors)
Consumers using Re.Pack / rspack (instead of Metro) were hitting:
[runtime not ready]: Error: Cannot find module 'react-native-sensitive-info/hooks'
__rspack_missing_module
./src/hooks/useAppInitializer.tsx
🔍 Root cause: the subpath exports in package.json declared a "react-native" condition that pointed at raw TypeScript source (./src/hooks/index.ts). Metro transpiles node_modules so it worked there, but Re.Pack / rspack don't transpile library source by default, so resolution failed at bundle time.
✅ Removed the "react-native" condition from the ./, ./hooks, and ./errors subpath exports — now matching the convention used by react-native-mmkv, react-native-reanimated, and @react-native-async-storage/async-storage. The top-level "react-native": "src/index" and "source": "src/index" fields are kept so Metro still gets source-map debugging for the main entry.
📂 Changed: package.json
🔒 Error API: full ES2022 cause parity
Building on the type-compatibility fix from v6.1.1, this release polishes SensitiveInfoError and HookError so their cause property is indistinguishable from a native ES2022 Error.
✨ cause is now non-enumerable
Previously assigned as a regular property (which would have made it enumerable, surface in Object.keys, and leak into JSON.stringify). Now installed via Object.defineProperty(this, 'cause', { writable: true, configurable: true, enumerable: false, ... }) — exactly how the native Error constructor does it.
✅ Logging, serialization, and DevTools error overlays no longer accidentally include the cause chain.
🧬 { cause: undefined } is now honored
Native new Error(msg, { cause: undefined }) still installs an own (non-enumerable) cause property. Both error classes now mirror that:
SensitiveInfoErroralready used'cause' in options✅HookErrorwas checkingcause !== undefined, which silently dropped the property — now also uses'cause' in options✅
🏷️ Type-only cause declaration for older TS targets
Added declare readonly cause?: unknown on both classes so consumers compiling with a tsconfig lib that predates ES2022 can still reference err.cause without TS errors. The declare form is fully erasable — no class field is emitted, so it can't accidentally become an enumerable own property.
📂 Changed: src/errors.ts, src/hooks/types.ts
🧪 Tests
Added thorough coverage in src/tests/errors.test.ts and src/tests/hooks.types.test.ts:
- ✅
causeis retained on the constructed error - ✅
causeis non-enumerable (descriptor check, hidden fromObject.keys, hidden fromJSON.stringify) - ✅
causeis not defined when no options are provided - ✅
causeis defined asundefinedwhen explicitly passed ({ cause: undefined }) — matching nativeError - ✅
causepropagates through subclasses (NotFoundError, etc.)
48 tests, all green. 💚
🛠️ Other Changes
🌿 CI workflow triggers aligned with the default branch
The repo's default branch is master, but test.yml, ios-build.yml, and android-build.yml were filtering push events on main, so those workflows weren't running on push to the actual default branch.
✅ Updated all three workflows to trigger on master.
📂 Changed: .github/workflows/test.yml, .github/workflows/ios-build.yml, .github/workflows/android-build.yml
⬆️ Upgrade
No code changes required — just bump:
yarn add react-native-sensitive-info@6.1.2
# or
npm install react-native-sensitive-info@6.1.2If you're using Re.Pack / rspack and were stuck on a workaround for the Cannot find module 'react-native-sensitive-info/hooks' error, you can now remove that workaround.
v6.1.1
🚀 v6.1.1
A small but important patch release that unblocks TypeScript builds for downstream consumers and modernizes the release pipeline. No API changes, no migration steps. 🎉
🐛 Bug Fixes
🛠️ Fixed TS2554 when compiling against older lib targets
Consumer projects whose tsconfig doesn't include the ES2022 lib were failing to type-check against our shipped src/ with:
node_modules/react-native-sensitive-info/src/errors.ts:75:18 - error TS2554: Expected 0-1 arguments, but got 2.
node_modules/react-native-sensitive-info/src/hooks/types.ts:41:18 - error TS2554: Expected 0-1 arguments, but got 2.
✅ SensitiveInfoError and HookError no longer pass the { cause } options object to super(). The cause is now assigned as a property after construction, so the library type-checks cleanly under any tsconfig lib target.
🔒 Runtime behavior is unchanged — error.cause is still populated when provided.
📂 Changed files: src/errors.ts, src/hooks/types.ts
🛠️ Other Changes
🔁 Migrated release tooling: semantic-release ➜ release-it
Consolidated to a single, conventional-OSS release flow that mirrors what most modern React Native libraries use:
- 🗑️ Removed
semantic-release,@semantic-release/changelog,@semantic-release/git, andconventional-changelog-cli. - ✨ Added
@release-it/conventional-changelogfor automatic version bumps andCHANGELOG.mdgeneration from conventional commits — preserving the previous emoji section grouping:- ✨ Features
- 🐛 Bug Fixes
- 💨 Performance Improvements
- 🔄 Code Refactors
- 📚 Documentation
- 🛠️ Other changes
- 📦
release-itconfig now lives inpackage.json(no more separate.release-it.json/release.config.cjs). - 🏷️ Single entry point:
npm run release(therelease:itscript was removed). - 🤖 GitHub Actions Release workflow rewritten to run
release-it --ciwith optionalworkflow_dispatchinputs:increment:patch/minor/major/prereleasedry-run: preview without publishing- Uses a bot git identity and enables npm provenance.
- 🧹 ~280 transitive dependencies removed (~11 MiB smaller lockfile).
⬆️ Upgrade
No code changes required — just bump:
yarn add react-native-sensitive-info@6.1.1
# or
npm install react-native-sensitive-info@6.1.1v6.1.0
🔐 react-native-sensitive-info v6.1.0
A focused follow-up to the v6 GA: smarter biometric capability detection, two production bug fixes that eliminate double biometric prompts, and a few DX/build polish items. Fully backward-compatible — apps reading only the biometry boolean keep working unchanged.
✨ What's new
👁️ Fine-grained biometric availability
A single biometry: boolean couldn't tell the difference between "no hardware", "hardware present but the user hasn't enrolled", and "ready to use" — three UX-distinct states. The new biometryStatus field on SecurityAvailability disambiguates them:
type BiometryStatus =
| 'available' // ✅ enrolled and usable right now
| 'notEnrolled' // 🟡 hardware OK, no fingerprint/face registered yet
| 'notAvailable' // 🚫 no hardware / admin-disabled / passcode unset
| 'lockedOut' // ⏳ too many failed attempts
| 'unknown' // ❓ probe could not classifyDrive a "Set up Face ID" CTA off 'notEnrolled' instead of hiding the toggle. Mapped natively from LAError codes on iOS and BiometricManager.canAuthenticate results on Android.
Invariant:
biometry === (biometryStatus === 'available').
🛡️ Policy precheck — canUseAccessControl
Predict whether a future setItem write with a given AccessControl policy will succeed before you try and trigger a prompt:
import { canUseAccessControl, setItem } from 'react-native-sensitive-info'
if (await canUseAccessControl('secureEnclaveBiometry')) {
await setItem('session', token, { accessControl: 'secureEnclaveBiometry' })
} else {
await setItem('session', token, { accessControl: 'devicePasscode' })
}Pass a snapshot you already hold to skip the native call entirely:
import { canUseAccessControlSync } from 'react-native-sensitive-info'
import { useSecurityAvailability } from 'react-native-sensitive-info/hooks'
const { data: caps } = useSecurityAvailability()
const canEnable = caps ? canUseAccessControlSync('secureEnclaveBiometry', caps) : false🔁 Foreground auto-refresh
Users commonly leave the app to enroll a fingerprint and come back — your toggle should catch up automatically:
const { data: caps } = useSecurityAvailability({ refreshOnForeground: true })Subscribes to AppState only when enabled, debounces back-to-back active transitions (~500 ms), and unsubscribes on unmount.
🔔 Enrollment listener — useBiometryStatusWatcher
Transition-only callback (fires once per real BiometryStatus change, never on every render):
import { useBiometryStatusWatcher } from 'react-native-sensitive-info/hooks'
useBiometryStatusWatcher((next, previous) => {
if (previous === 'notEnrolled' && next === 'available') {
showToast('Face ID is ready.')
}
})Lives in its own module so apps that don't watch enrollment changes don't pay for it (sideEffects: false keeps tree-shaking honest).
🐛 Bug fixes
📱 iOS — no more double Face ID / Touch ID prompt on getItem
The lazy re-encryption path that runs after a successful authenticated read used to call SecItemUpdate against the same Keychain item to refresh its key-version metadata; iOS treats that as a separate authorization gate, prompting the user a second time. Biometric items now skip the lazy refresh entirely and are upgraded only by an explicit setItem or rotateKeys({ reEncryptEagerly: true }). Non-biometric items continue to be upgraded silently.
🤖 Android — same double-prompt regression, fixed
Lazy re-encryption inside getItem allocated a new key alias for the active version and Cipher.init on that fresh setUserAuthenticationRequired(true) key required its own biometric authorization, surfacing as a second prompt right after the read. The lazy refresh now skips entries with requiresAuthentication == true (and any biometry-class access policy).
🪪 iOS — errSecDuplicateItem on setItem, fixed
setItem no longer fails with "The specified item already exists in the keychain" when:
- the caller toggles
iosSynchronizablebetween writes, or - iCloud Keychain restores an entry between our delete and add.
The internal upsert helper now wipes prior entries with kSecAttrSynchronizableAny and absorbs the iCloud-restore race with a single bounded retry. Bundle ID + access group already scope the partition, so the overwrite never crosses an app or sharing boundary.
🧰 Tooling & DX
- ⚛️
babel-plugin-react-compilernow actually runs on the published bundle. Thereact-native-builder-bobtargets opt intoconfigFile: trueso they pick up the library'sbabel.config.js, which branches on the bob caller and pairs the compiler with the right preset. - 🚦 Four hooks that coordinate refs across renders (
useStableOptions,useAsync,useMutation,useSecureStorage) carry an explicit'use no memo'opt-out — the compiler can't preserve their structural-stability guarantees and now skips them deliberately.
📚 Docs
- Clarify
SecurityAvailability.secureEnclavesemantics across platforms (Secure Enclave on iOS / mirrorsstrongBoxon Android) — gate "hardware-backed key" UX without branching onPlatform.OS. - README + CHANGELOG correctly state that
canUseAccessControl(policy, levels?)only skips the native call when a snapshot is supplied. - Android
requiresBiometricAuthdoc comment now matches the actual classification.
🧨 Breaking changes
None. 🎉
🙌 Upgrade
npm install react-native-sensitive-info@6.1.0
# or
yarn add react-native-sensitive-info@6.1.0Then re-run pod install from ios/ and rebuild.
Full diff: v6.0.0...v6.1.0
v6.0.0
🔐 v6.0.0 — Release Notes (5.6.2 → 6.0.0) 🚀
react-native-sensitive-info 6 is a from-scratch rewrite on top of Nitro Modules and the React Native New Architecture. It is not a drop-in upgrade from 5.6.2 — the API surface is intentionally narrower, fully typed, and metadata-rich. Plan a migration window and use the migration guide.
⚡ TL;DR
- New runtime: Nitro hybrid object replaces the legacy bridge module. Requires React Native ≥ 0.80 with the New Architecture enabled.
- Promise-based API throughout; every read/write returns rich
StorageMetadata. - Typed errors (
SensitiveInfoErrorsubclasses) withinstanceofpredicates — no more string-matching. - First-class React hooks under
react-native-sensitive-info/hooks. - Versioned key rotation with lazy re-encryption.
- Defense-in-depth hardening: HMAC integrity tag, AES-GCM AAD binding,
setUnlockedDeviceRequired, plaintext zeroization, constant-time comparisons. - Tree-shakeable subpath exports (
.,/hooks,/errors);"sideEffects": false. - Windows is no longer supported. v6 targets Android + Apple platforms (iOS, macOS, visionOS, watchOS).
- First-class Expo config plugin — Face ID usage description, biometric permissions, and New Architecture flags wired up automatically. Expo Go is not supported; use a Dev Client or EAS Build.
🤔 Why upgrade?
| 5.6.2 | 6.0.0 | |
|---|---|---|
| Runtime | Legacy bridge module (Paper-compatible) | Nitro hybrid object (New Architecture only) |
| Architecture | Old + Fabric (bridge fallback) | New Architecture required |
| API surface | Mixed sync/async, manual flag soup (kSecAccessControl*, keystore) |
Promise-based, typed, single accessControl enum |
| Return types | void writes, raw value reads |
MutationResult writes, SensitiveInfoItem | null reads with metadata |
| Errors | Plain Error instances, message string-matching |
Typed subclasses + is*Error predicates, dedicated /errors subpath |
| Metadata | None | securityLevel, backend, accessControl, keyVersion, integrityTag, timestamp |
| Key rotation | Not available | rotateKeys() + getKeyVersion() with lazy / eager re-encryption |
| Integrity | AES-GCM tag only | AES-GCM tag + HMAC-SHA256 over (service, key, version, accessControl, securityLevel, timestamp, ciphertext, iv) |
| Replay/swap defense | None | AES-GCM AAD bound to service|key|v<version> (Android); kSecAttrService + kSecAttrAccount (iOS) |
| Plaintext lifetime | Best-effort | Zeroized on both platforms after encrypt/decrypt |
| React hooks | None | useSecret, useSecretItem, useHasSecret, useSecureStorage, useSecurityAvailability, useKeyRotation, useSecureOperation |
| Expo support | None / community plugin | First-party app.plugin.js (Info.plist, AndroidManifest, new-arch flags) |
| Windows support | Yes (legacy) | Removed |
| Bundle size | Single CJS entry | "sideEffects": false, subpath exports, no default export |
| Lint/format toolchain | ESLint + Prettier | Biome 2 (single config) |
| TypeScript | Loose typings | Strict, exactOptionalPropertyTypes, declaration maps |
✨ Highlights
🏎️ Nitro hybrid core (RN 0.80+ / New Architecture)
The core moves from the legacy module bridge to a Nitro hybrid object. Native calls bypass the JS bridge serialization layer, so secure-storage operations run with significantly lower marshalling overhead and predictable latency.
- iOS / Apple platforms: Swift + CryptoKit + Keychain, Secure Enclave-gated AES-GCM.
- Android: Kotlin + Keystore (StrongBox-aware) with
EncryptedSharedPreferencessoftware fallback.
📦 Promise-based API with metadata
import { setItem, getItem } from 'react-native-sensitive-info'
const result = await setItem('session-token', 'super-secret', {
service: 'auth',
accessControl: 'secureEnclaveBiometry',
})
// result.metadata: { securityLevel, backend, accessControl, keyVersion, integrityTag, ... }
const item = await getItem('session-token', { service: 'auth' })
// item: { key, service, value?, metadata } | nullEvery read/write returns StorageMetadata, so apps can confirm the actual security level applied (e.g. refuse to store secrets when metadata.securityLevel === 'software').
🚨 Typed errors
import {
isAuthenticationCanceledError,
isIntegrityViolationError,
isKeyInvalidatedError,
} from 'react-native-sensitive-info/errors'
try {
await getItem('token', { service: 'auth' })
} catch (error) {
if (isAuthenticationCanceledError(error)) return
if (isKeyInvalidatedError(error)) {
// Hardware key invalidated (e.g. biometrics re-enrolled)
}
throw error
}Predicates: isNotFoundError, isAuthenticationCanceledError, isIntegrityViolationError, isKeyInvalidatedError, isRotationFailedError, isInvalidArgumentError.
⚛️ React hooks
A focused hooks API ships at react-native-sensitive-info/hooks:
| Hook | Use case |
|---|---|
useSecureStorage |
List + CRUD over a service |
useSecret |
Single secret + save/delete |
useSecretItem |
Single secret read |
useHasSecret |
Lightweight existence check |
useSecurityAvailability |
Device capability snapshot |
useKeyRotation |
Bump and inspect master-key version |
useSecureOperation |
Wrap arbitrary imperative calls in a state machine |
All hooks share the same lifecycle/abort/error pipeline (useAsyncQuery, useMutation) and never tear state on unmount.
🔁 Versioned key rotation
import { rotateKeys, getKeyVersion } from 'react-native-sensitive-info'
await rotateKeys({ service: 'auth' }) // lazy, upgrades on next read
await rotateKeys({ service: 'auth', reEncryptEagerly: true }) // eager
const version = await getKeyVersion({ service: 'auth' })- iOS:
SecItemUpdatepreserves existing access-control attributes while bumpingkeyVersion. - Android: mints a fresh per-entry Keystore alias (
SensitiveInfo_<hash>_v<version>) and deletes the stale one after a successful rewrite.
🛡️ Defense-in-depth hardening
Applied transparently to new writes and via lazy upgrade on rotation. Backwards compatible — entries written by older 6.x RC builds decode without verification and are upgraded on the next write or rotation.
- HMAC-SHA256 integrity tag bound to every entry's metadata + ciphertext (
StorageMetadata.integrityTag). Tampering raisesIntegrityViolationErrorbefore any biometric prompt is shown. - AES-GCM AAD on Android binds ciphertext to
service|key|v<version>, defeating cross-entry swap attacks. setUnlockedDeviceRequired(true)on every Android Keystore key (API 28+), mirroring iOSkSecAttrAccessibleWhenUnlockedsemantics.- Plaintext byte buffers zeroized after use on both platforms.
- Constant-time HMAC comparison (
MessageDigest.isEqual/ manualUInt8XOR fold).
🌳 Tree-shaking & ESM-first packaging
"sideEffects": false everywhere, with three focused subpath entries:
| Import | Contents |
|---|---|
react-native-sensitive-info |
setItem, getItem, hasItem, deleteItem, getAllItems, clearService, getSupportedSecurityLevels, rotateKeys, getKeyVersion, types |
react-native-sensitive-info/hooks |
Every React hook |
react-native-sensitive-info/errors |
Typed error classes + instanceof predicates |
The default export is gone — use named imports.
🧩 Expo config plugin
A first-party app.plugin.js ships in the package. Add it to app.json / app.config.ts:
{
"expo": {
"plugins": [
["react-native-sensitive-info", {
"faceIDPermission": "Authenticate to unlock your account.",
"enableNewArchitecture": true
}]
]
}
}Handles: NSFaceIDUsageDescription, USE_BIOMETRIC + legacy USE_FINGERPRINT (with maxSdkVersion=28), and New Architecture flags (newArchEnabled, RCT_NEW_ARCH_ENABLED). See docs/EXPO.md.
💥 Breaking changes
A migration table for the most common call sites:
| 5.6.2 | 6.0.0 |
|---|---|
| Bridge module, Old Architecture compatible | Nitro hybrid object, New Architecture required |
setItem(key, value, options) returns void |
setItem(key, value, options) returns Promise<MutationResult> |
getItem(key, options) returns the raw value or null |
getItem(key, options) returns SensitiveInfoItem | null ({ key, service, value, metadata }) |
getAllItems(options) returns Record<string, string> |
getAllItems(options) returns SensitiveInfoItem[] |
deleteItem(key, options) returns void |
deleteItem(key, options) returns Promise<boolean> |
sharedPreferencesName (Android) / keychainService (iOS) |
unified as service |
kSecAccessControl* strings + Android keystore config object |
single accessControl: 'secureEnclaveBiometry' | 'biometryCurrentSet' | 'biometryAny' | 'devicePasscode' | 'none' |
setInvalidatedByBiometricEnrollment opt-out |
Always-on; biometric-bound entries raise KeyInvalidatedError deterministically |
Errors are plain Error instances |
Typed SensitiveInfoError subclasses + is*Error predicates |
| Default + named exports from package root | Named exports only; no default export; hooks moved to /hooks |
| Windows supported | Windows removed |
🔑 Access-control flag mapping
5.6.2 (kSecAccessControl…) |
6.0.0 (accessControl) |
|---|---|
kSecAccessControlBiometryCurrentSet + Secure Enclave class |
secureEnclaveBiometry |
kSecAccessControlBiometryCurrentSet |
biometryCurrentSet |
kSecAccessControlBiometryAny |
biometryAny |
kSecAccessControlDevicePasscode |
devicePasscode |
| (no policy) | none |
🚚 Data migrati...
v6.0.0-rc.12
- Refactor code structure for improved readability and maintainability (e515a9d)
- fix: update repository field format in package.json (eeadcb8)
- Merge pull request #532 from mCodex/feat/androidBiometric14 (0c7796a)
- Make HybridSensitiveInfo class and methods public (5bdbb63)
- Update Android biometric lib and improve null handling (9023957)
- feat: restructure app components and implement secure storage functionality (b84ec82)
What's Changed
Full Changelog: v6.0.0-rc.9...v6.0.0-rc.12
v6.0.0-rc.9
Changelog:
- chore: normalize formatting in hooks (semicolons/spacing) and bump example iOS Podfile.lock deps (c9087e6)
- Merge pull request #494 from mCodex/refactor/appExample (ed1b003)
- docs: add error-handling section to README and introduce SECURITY.md (69876ac)
- fix(auth): treat authentication cancellations as soft-failures and map native cancel codes (4454883)
- chore: bump dev/example deps and normalize README version notation (0c6bd68)
- Update GitHub Sponsors username in FUNDING.yml (c495d01)
- example: overhaul App.tsx to use hooks-based playground UI (4d3540f)
Release 5.6.2
5.6.2 (2025-11-03)
- chore: bump devDependencies and regenerate lockfiles (006236a)
- Refactor entry point and enhance native fallback logic (91916b1)
- Update src/index.ts (b27dda4)
- Update src/index.ts (04aece9)
What's Changed
Full Changelog: v5.6.1...v5.6.2
Release 5.6.1
5.6.1 (2025-10-29)
Fixes [#480 ]
- Refactor iOS access control and Keychain handling; add RN Swift module & ObjC bridge; update deps an (4ccb7f1)
Full Changelog: v5.6.0...v5.6.1