@beatsphere/expo-secure-token-manager
v0.1.0
Published
Resilient wrapper around expo-secure-store with retry logic for iOS Keychain and Android Keystore errors
Maintainers
Readme
@beatsphere/expo-secure-token-manager
Resilient wrapper around expo-secure-store with automatic retry logic for iOS Keychain and Android Keystore errors. Battle-tested in BeatSphere.
Why
expo-secure-store throws on several real-world edge cases:
- iOS:
"User interaction is not allowed"when accessing the Keychain during background execution or while the device is locked - Android:
"Could not encrypt/decrypt"when the Keystore encryption key becomes corrupted (happens on some devices after OS updates or factory reset recovery)
This wrapper catches these errors, retries with backoff, and on Android write failures, deletes the corrupted key to force the Keystore to create a fresh encryption key.
Install
npm install @beatsphere/expo-secure-token-managerUsage
import {
getSecureItem,
setSecureItem,
deleteSecureItem,
} from '@beatsphere/expo-secure-token-manager';
// Store a token — returns true on success, false on failure
await setSecureItem('auth_token', 'eyJhbGciOiJIUzI1NiIs...');
// Retrieve a token — returns null on failure instead of throwing
const token = await getSecureItem('auth_token');
// Delete a token
await deleteSecureItem('auth_token');Configuration (optional)
import { configure } from '@beatsphere/expo-secure-token-manager';
import * as SecureStore from 'expo-secure-store';
configure({
maxRetries: 5, // default: 3
retryDelay: 200, // default: 100ms (multiplied by attempt number)
keychainAccessible: SecureStore.WHEN_UNLOCKED, // default: AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY
});API
| Function | Returns | Description |
|----------|---------|-------------|
| configure(opts) | void | Set retry and Keychain options |
| getSecureItem(key) | Promise<string \| null> | Get value with retry. Returns null on failure |
| setSecureItem(key, value) | Promise<boolean> | Set value with retry. On Android encryption error, deletes corrupted key and retries |
| deleteSecureItem(key) | Promise<boolean> | Delete value. Returns true even on error |
How retry works
Read (getSecureItem)
- Attempt
SecureStore.getItemAsync - On Android decryption error → retry (does NOT delete, that would destroy the value)
- On iOS Keychain error → retry
- After max retries → return
null
Write (setSecureItem)
- Attempt
SecureStore.setItemAsync - On Android encryption error → delete the key (resets corrupted encryption state) → retry
- On iOS Keychain error → retry
- After max retries → return
false
License
MIT
