React API reference
Complete reference for Ownfold's SSR-safe provider and every headless encryption, recovery, device, pairing, and rotation hook.
@ownfold/react is strictly headless. It renders no controls, supplies no text, and ships no CSS.
Your application decides the name of the feature, its description, which lifecycle actions are
visible, and how every state is presented.
pnpm add @ownfold/browser @ownfold/react
Add the provider
Create one browser client and provide it above the application-owned controls.
"use client"
import { VaultProvider } from "@ownfold/react"
import type { ReactNode } from "react"
import { vault } from "@/lib/vault-client"
export function EncryptionProvider({ children }: { readonly children: ReactNode }) {
return (
<VaultProvider client={vault} onInitializationError={reportSafeVaultError}>
{children}
</VaultProvider>
)
}
VaultProvider is SSR-safe. Initialization runs after hydration by default. Set initialize={false}
only when the host owns the initialization lifecycle. onInitializationError receives a typed
OwnfoldError; do not log secrets, ciphertext, or decrypted values with it.
Provider behavior
| Event | Behavior |
|---|---|
| Server render | Provides the caller-owned client through context without running initialization or another browser side effect. |
| First client effect | Calls client.initialize() when initialize is not false. |
| Initialization success | Client state subscription updates every consuming hook. |
| Initialization error | Calls onInitializationError when supplied; the typed client state remains authoritative. |
| Client prop replacement | Unsubscribes from the previous client and binds the new instance. The provider does not dispose caller-owned clients. |
| Unmount | Removes React subscriptions. It does not delete vault storage or revoke a device. |
Every hook throws a framework-boundary error when called outside VaultProvider. This programmer
error is distinct from expected Ownfold operation failures, which remain Result values.
Hook reference
| Hook | Values and operations | Use it when |
|---|---|---|
useVault() |
The current VaultClient. |
The host needs lifecycle methods not wrapped by another hook, such as createVault() or destroyLocalState(). |
useVaultStatus() |
The current discriminated VaultState. |
Rendering controls from explicit lifecycle states. |
useVaultLock() |
state, lock(), unlock(secret?). |
Adding lock and unlock controls. |
useRecoveryKit() |
create, get, generateCode, restore, verify, prepareReplacement, completeReplacement, getPendingReplacement. |
Building onboarding, restore, or replacement flows. |
useVaultDevices() |
current, list, revoke. |
Showing and revoking enrolled browsers. |
useVaultPairing() |
begin, refresh, approve, cancel. |
Building an existing-device approval flow. |
useKeyRotation(records, options?) |
prepare, complete, getPendingRecoveryKit. |
Rotating the root key after the host supplies a RotationRecordStore. |
useKeyRotationProgress() |
A discriminated rotation progress snapshot. | Showing resumable rotation phases and processed-record counts. |
Every fallible operation preserves the browser client’s typed Result value. Hooks never turn an
error into a success-shaped fallback and never download a file as a side effect.
Exact hook contracts
useVault()
Returns the provider’s stable VaultClient identity. Use it for createVault, encryptJson,
decryptJson, binary operations, subscriptions, or less common lifecycle methods. Do not call
dispose() on a shared provider client from a child component.
useVaultStatus()
Subscribes with React’s external-store contract and returns the current VaultState. Components
rerender when the state object changes. Switch on status; do not copy the state into a second local
state machine.
useVaultLock()
| Member | Signature | Behavior |
|---|---|---|
state |
VaultState |
Same reactive snapshot as useVaultStatus. |
lock |
() => VaultClientResult<VaultState> |
Synchronously destroys active root-key access and broadcasts the lock. |
unlock |
(secret?: string) => Promise<VaultClientResult<VaultState>> |
Opens the enrolled device envelope, or uses recovery only in a state that supports the optional secret. |
useRecoveryKit()
| Member | Result | Side effect boundary |
|---|---|---|
generateCode() |
Result<string, CryptoEngineError> |
Generates a local secret; does not persist or display it. |
create(secret) |
RecoveryKitV1 |
Creates pending local/remote vault metadata; does not download. |
get() |
RecoveryKitV1 |
Reads the locally retained onboarding kit. |
verify(contents, secret) |
VaultState |
Parses and opens the imported file, compares root keys, then enrolls the device. |
restore(contents, secret) |
VaultState |
Restores an unavailable device from an existing kit. |
prepareReplacement(secret) |
RecoveryKitV1 |
Creates a pending replacement without changing the authoritative kit. |
getPendingReplacement() |
`PendingRecoveryKitReplacement | null` |
completeReplacement(contents, secret) |
VaultState |
Verifies the downloaded replacement before committing metadata. |
useVaultDevices()
| Member | Result |
|---|---|
current() |
Current local device ID or null. |
list() |
Remote-safe readonly DeviceSummary[]. |
revoke(deviceId, expectedRevision) |
Updated DeviceSummary. |
The hook does not ask for confirmation. The application owns confirmation UI and must refresh after a revision conflict. Revoking the current device removes its future remote authorization; local lock/state behavior still follows the client lifecycle.
useVaultPairing()
| Member | Result | Intended device |
|---|---|---|
begin() |
PairingOfferV1 |
New device creates and shares the offer. |
refresh() |
VaultState |
New device polls/reconciles request status. |
approve(offer) |
PairingRequest |
Existing unlocked device parses the offer and creates the device envelope. |
cancel() |
VaultState |
New device cancels its current pending request. |
useKeyRotation(records, options?)
records implements RotationRecordStore. options.batchSize controls records per persisted
checkpoint; it does not weaken server transaction requirements.
| Member | Result |
|---|---|
prepare(secret) |
Replacement RecoveryKitV1 for download and re-import. |
getPendingRecoveryKit() |
Pending kit or null after reload. |
complete(contents, secret) |
Final VaultState after kit verification, record rewrap, device envelope replacement, and server cutover. |
useKeyRotationProgress()
Returns RootKeyRotationProgress, including phase, processed count, and total when known. It
subscribes independently from general vault state so progress UI updates during long batches without
inventing a second polling loop.
Build only the controls you need
import { useVault, useVaultLock, useVaultStatus } from "@ownfold/react"
export function PrivacySettings() {
const vault = useVault()
const state = useVaultStatus()
const { lock, unlock } = useVaultLock()
return (
<section aria-labelledby="privacy-heading">
<h2 id="privacy-heading">Private workspace</h2>
<p>Encryption state: {state.status}</p>
{state.status === "not-created" && (
<button type="button" onClick={() => void vault.createVault()}>
Turn on encryption
</button>
)}
{state.status === "ready-unlocked" && (
<button type="button" onClick={() => lock()}>Lock</button>
)}
{state.status === "ready-locked" && (
<button type="button" onClick={() => void unlock()}>Unlock this browser</button>
)}
</section>
)
}
The example deliberately owns the heading, description, button labels, and state visibility. Rename or omit any of them without configuring Ownfold because presentation is not part of the SDK interface.
Recovery Kit files
useRecoveryKit().create(secret) returns a RecoveryKitV1; it does not download it. The host can
use RecoveryKitFile.download(result.value) or its own file workflow. Re-import the actual saved
file before calling verify(contents, secret) so onboarding proves the user’s recovery artifact.
const recovery = useRecoveryKit()
const created = await recovery.create(secret)
if (created.status === "error") return showError(created.error)
RecoveryKitFile.download(created.value, "my-product-recovery.json")
const contents = await selectedFile.text()
const verified = await recovery.verify(contents, secret)
if (verified.status === "error") return showError(verified.error)
generateCode() produces a cryptographically random recovery code for applications that prefer a
generated secret. get() reads the locally retained kit. Replacement remains a two-step
prepare/download/import/complete flow; getPendingReplacement() lets the host resume it after a
reload.
Pairing, devices, and rotation
Pairing offers are application data. Render them as text, a host-selected QR library, a share sheet, or another channel appropriate to the product. Ownfold provides only the typed offer and actions.
Device list entries contain metadata such as labels and last-active timestamps. The application chooses which fields to show and must confirm destructive revocation actions itself.
Rotation is opt-in because Ownfold cannot discover application records. Pass the host’s
RotationRecordStore to useKeyRotation; download and re-import the replacement Recovery Kit
before complete. Use useKeyRotationProgress to render the phases that matter to the product.
Security constraints
- Never send recovery passwords, imported Recovery Kits, or decrypted data to analytics, logs, server actions, or error reporting.
- Render from the discriminated state instead of enabling actions in invalid states.
- Preserve typed errors and tell the user what remains safe and what recovery action to take.
- Treat automatic locking as key-lifetime reduction, not protection from malicious same-origin JavaScript.
See the browser client reference for every lifecycle method and state, and Accessibility for requirements that now belong to the host interface.