Encrypted persistent state for React applications. Secure secrets locally. No backend. No WebCrypto. No leaks.
npm install @p47h/vault-reactlocalStorage is readable by any injected script
@p47h/vault-react is a React integration for P47H Vault, a browser-side encrypted storage powered by a Rust core compiled to WebAssembly.
bash
npm install @p47h/vault-react
`
---
Quick Start
$3
`tsx
import { P47hProvider } from '@p47h/vault-react';
export default function App() {
return (
Loading vault…
That's it.
No WASM config. No async bootstrapping. No globals.
---
$3
`tsx
import { useIdentity } from '@p47h/vault-react';
function Login() {
const { register, login, logout, isAuthenticated, isLoading } = useIdentity();
if (isAuthenticated) {
return ;
}
return (
);
}
`
---
$3
`tsx
import { useSecret } from '@p47h/vault-react';
function SecretNote() {
const { value, set, status, locked } = useSecret('my_private_note');
if (locked) return Vault locked;
if (status === 'loading') return Decrypting…;
return (
value={value ?? ''}
onChange={e => set(e.target.value)}
placeholder="This text is encrypted locally"
/>
);
}
`
That's it.
You are now storing encrypted data in the browser
without touching cryptography.
---
Mental Model
Think of useSecret() as:
> useState() — but encrypted and persisted securely.
| React | P47H |
| ------------ | ------------- |
| useState() | useSecret() |
| Plaintext | Encrypted |
| JS memory | WASM memory |
| Unsafe | Hardened |
---
API Overview
$3
`tsx
config={optionalConfig}
fallback={ }
errorFallback={(error) => }
/>
`
Handles:
* WASM loading
* Vault lifecycle
* Global error handling
* React Strict Mode compatibility
---
$3
`ts
const {
did,
isAuthenticated,
login,
register,
logout,
recover,
isLoading,
error,
storedIdentities
} = useIdentity();
`
Manages:
* Vault unlock / lock
* Identity lifecycle
* Recovery code generation
* Multiple identity support
---
$3
`ts
const {
value, // string | null - The decrypted value
set, // (v: string) => void - Save (auto-encrypts)
status, // 'idle' | 'loading' | 'saving' | 'error'
exists, // boolean - true if value !== null
locked, // boolean - true if !identity
error // Error | null
} = useSecret('secret_key');
`
States:
* locked → vault not unlocked (show lock UI)
* status === 'loading' → decrypting in progress
* status === 'saving' → encrypting/persisting
* exists → secret exists vs empty placeholder
---
Advanced Usage
$3
`tsx
function RegisterForm() {
const { register } = useIdentity();
const [recoveryCode, setRecoveryCode] = useState(null);
const handleRegister = async (password: string) => {
const result = await register(password);
setRecoveryCode(result.recoveryCode);
// ⚠️ IMPORTANT: Show this to the user!
};
if (recoveryCode) {
return (
⚠️ Save your Recovery Code!
{recoveryCode}
This is the ONLY way to recover your vault
if you forget your password.
);
}
// ... registration form
}
`
$3
`tsx
function SettingsPanel() {
const apiKey = useSecret('api_key');
const privateNote = useSecret('private_note');
const encryptedToken = useSecret('auth_token');
// Each secret is independent and cached
return (
apiKey.set(e.target.value)} />
);
}
`
$3
`tsx
function SecretInput({ secretKey }: { secretKey: string }) {
const { value, set, status, exists, error } = useSecret(secretKey);
return (
type="password"
value={value ?? ''}
onChange={(e) => set(e.target.value)}
placeholder={exists ? '••••••••' : 'Enter value'}
disabled={status === 'loading'}
/>
{status === 'loading' && }
{status === 'saving' && 💾 Saving...}
{status === 'idle' && exists && ✅ Saved}
{status === 'error' && ❌ {error?.message}}
);
}
`
---
Security Model (Short Version)
* Keys are derived using Argon2id
* Secrets are encrypted with XChaCha20-Poly1305
* Private keys live inside WASM linear memory
* Secrets are never stored or transmitted in plaintext
* Memory is wiped on vault lock
This is not WebCrypto wrappers.
This is a compiled Rust core.
---
Architecture
`text
┌─────────────────────────────────────────────────────────┐
│ Your React App │
├─────────────────────────────────────────────────────────┤
│ useSecret() useIdentity() useP47h() │
├─────────────────────────────────────────────────────────┤
│ P47hProvider │
│ (Context + State Management) │
├─────────────────────────────────────────────────────────┤
│ VaultController │
│ (Internal - Strict Mode + SSR safe) │
├─────────────────────────────────────────────────────────┤
│ @p47h/vault-js │
│ (VaultService) │
├─────────────────────────────────────────────────────────┤
│ WASM Module │
│ (Rust - Argon2id + XChaCha20) │
└─────────────────────────────────────────────────────────┘
`
---
When should I use this?
Use @p47h/vault-react if you need to:
* Store API keys in frontend apps
* Handle sensitive user data locally
* Build offline-first or privacy-first apps
* Avoid backend secret storage
* Reduce GDPR exposure
Do not use it if:
* You need shared secrets across users
* You want server-side access to the data
---
TypeScript Support
Full TypeScript support with strict types:
`typescript
import type {
UseSecretReturn,
UseIdentityReturn,
VaultState,
SecretStatus
} from '@p47h/vault-react';
// All hooks are fully typed
const secret: UseSecretReturn = useSecret('key');
const identity: UseIdentityReturn = useIdentity();
`
---
License
Apache License 2.0
* ✔ Commercial use allowed
* ✔ Closed-source apps allowed
* ✔ No telemetry
* ✔ No phone home
You control your data. Always.
---
Related
* Core engine: p47h-open-core (Rust / WASM)
* JS SDK: @p47h/vault-js`