npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@p47h/vault-react

v0.10.1

Published

Encrypted persistent state for React applications. Secure secrets locally. No backend. No WebCrypto. No leaks.

Readme

@p47h/vault-react

Encrypted persistent state for React applications. Secure secrets locally. No backend. No WebCrypto. No leaks.


Why?

Storing secrets in the browser is broken.

  • localStorage is readable by any injected script
  • Cookies are sent over the network
  • Environment variables leak at build time
  • WebCrypto APIs are easy to misuse

P47H Vault React gives you a secure, local-first vault backed by WebAssembly and modern cryptography — exposed as simple React hooks.

You think in state. We handle encryption.


What is it?

@p47h/vault-react is a React integration for P47H Vault, a browser-side encrypted storage powered by a Rust core compiled to WebAssembly.

It lets you store and use secrets (API keys, private data, notes, tokens) without ever exposing them in plaintext to the JavaScript heap.

No server required. Works offline.


Features

  • End-to-end local encryption (Argon2id + XChaCha20-Poly1305)
  • WASM-backed core (Rust, not JavaScript crypto)
  • React-first DX (Context + hooks)
  • Zero configuration (no WASM plumbing)
  • Memory hygiene (keys zeroized on lock)
  • Framework friendly (React, Next.js, Vite)
  • Strict Mode compatible (no double-mount issues)
  • Race condition proof (stale request handling)

Installation

npm install @p47h/vault-react

Quick Start

1. Wrap your app

import { P47hProvider } from '@p47h/vault-react';

export default function App() {
  return (
    <P47hProvider fallback={<div>Loading vault…</div>}>
      <MyApp />
    </P47hProvider>
  );
}

That's it. No WASM config. No async bootstrapping. No globals.


2. Manage identity

import { useIdentity } from '@p47h/vault-react';

function Login() {
  const { register, login, logout, isAuthenticated, isLoading } = useIdentity();

  if (isAuthenticated) {
    return <button onClick={logout}>Lock vault</button>;
  }

  return (
    <button onClick={() => login('user-password')}>
      Unlock vault
    </button>
  );
}

3. Store secrets (the magic part)

import { useSecret } from '@p47h/vault-react';

function SecretNote() {
  const { value, set, status, locked } = useSecret('my_private_note');

  if (locked) return <div>Vault locked</div>;
  if (status === 'loading') return <div>Decrypting…</div>;

  return (
    <textarea
      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

<P47hProvider />

<P47hProvider
  config={optionalConfig}
  fallback={<Loading />}
  errorFallback={(error) => <ErrorPage error={error} />}
/>

Handles:

  • WASM loading
  • Vault lifecycle
  • Global error handling
  • React Strict Mode compatibility

useIdentity()

const {
  did,
  isAuthenticated,
  login,
  register,
  logout,
  recover,
  isLoading,
  error,
  storedIdentities
} = useIdentity();

Manages:

  • Vault unlock / lock
  • Identity lifecycle
  • Recovery code generation
  • Multiple identity support

useSecret(key)

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

Registration with Recovery Code

function RegisterForm() {
  const { register } = useIdentity();
  const [recoveryCode, setRecoveryCode] = useState<string | null>(null);

  const handleRegister = async (password: string) => {
    const result = await register(password);
    setRecoveryCode(result.recoveryCode);
    // ⚠️ IMPORTANT: Show this to the user!
  };

  if (recoveryCode) {
    return (
      <div className="recovery-warning">
        <h2>⚠️ Save your Recovery Code!</h2>
        <code>{recoveryCode}</code>
        <p>
          This is the ONLY way to recover your vault 
          if you forget your password.
        </p>
      </div>
    );
  }

  // ... registration form
}

Multiple Secrets

function SettingsPanel() {
  const apiKey = useSecret('api_key');
  const privateNote = useSecret('private_note');
  const encryptedToken = useSecret('auth_token');

  // Each secret is independent and cached
  return (
    <div>
      <input value={apiKey.value ?? ''} onChange={e => apiKey.set(e.target.value)} />
      <textarea value={privateNote.value ?? ''} onChange={e => privateNote.set(e.target.value)} />
      {/* ... */}
    </div>
  );
}

Status-based UI

function SecretInput({ secretKey }: { secretKey: string }) {
  const { value, set, status, exists, error } = useSecret(secretKey);

  return (
    <div className="secret-input">
      <input
        type="password"
        value={value ?? ''}
        onChange={(e) => set(e.target.value)}
        placeholder={exists ? '••••••••' : 'Enter value'}
        disabled={status === 'loading'}
      />
      
      <div className="status">
        {status === 'loading' && <Spinner />}
        {status === 'saving' && <span>💾 Saving...</span>}
        {status === 'idle' && exists && <span>✅ Saved</span>}
        {status === 'error' && <span>❌ {error?.message}</span>}
      </div>
    </div>
  );
}

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

┌─────────────────────────────────────────────────────────┐
│                    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:

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
  • Website: https://p47h.com

Final note

This library exists because existing tools were not secure enough.

We built it for production use first. Then we open-sourced it.