Skip to content

React integration

Status: Runnable from source for basic single-identity hooks; sharing is incomplete.

The packages are unpublished. Build and consume the repository workspaces as shown in the quick start. The current password-manager source typechecks, builds, and passes a Chromium startup smoke test:

Terminal window
yarn build
yarn workspace @peerborne/password-manager build
yarn test:e2e:password-manager

This evidence covers startup, not sharing or cross-browser convergence.

usePeerborneDocumentState requires four caches and four setters backed by React state:

function SwarmDocumentProvider({ children }: { children: React.ReactNode }) {
const [docCache, setDocCache] = React.useState<Record<string, any>>({});
const [docDataCache, setDocDataCache] = React.useState<Record<string, any>>({});
const [docReadersCache, setDocReadersCache] = React.useState<Record<string, any[]>>({});
const [docWritersCache, setDocWritersCache] = React.useState<Record<string, any[]>>({});
return (
<PeerborneContext.Provider value={{
docCache,
docDataCache,
docReadersCache,
docWritersCache,
setDocCache,
setDocDataCache,
setDocReadersCache,
setDocWritersCache,
}}>
{children}
</PeerborneContext.Provider>
);
}

The default context setters are no-ops; omitting this provider prevents cache updates from driving renders.

Create provider instances outside render, then pass stable ECDSA P-384 signing keys to the current hook signature:

const crdt = new YjsProvider();
const serializer = new YjsJSONSerializer();
const auth = new SubtleCrypto();
const acl = new YjsACLProvider();
const keychain = new YjsKeychainProvider();
const swarm = usePeerborne(
privateKey,
publicKey,
crdt,
serializer,
serializer,
serializer,
auth,
acl,
keychain,
defaultConfig(defaultBootstrapConfig(
import.meta.env.VITE_RELAY_MULTIADDR
? [import.meta.env.VITE_RELAY_MULTIADDR]
: [],
)),
);

The application must persist and restore the signing keys. New keys mean a new ACL identity. usePeerborne re-runs when key object identity changes, but it has no effect cleanup that stops the previous swarm; do not rotate or reconstruct keys during normal rendering.

import type { Peerborne } from '@peerborne/core';
import type * as Y from 'yjs';
type YjsSwarm = Peerborne<
Y.Doc,
Uint8Array,
(doc: Y.Doc) => void,
CryptoKey,
CryptoKey,
CryptoKey
>;
function Note({ swarm }: { swarm: YjsSwarm }) {
const [doc, changeDoc, acl, activationError] = usePeerborneDocumentState(
swarm,
'/notes/hello',
'all',
'create',
);
if (activationError) return <p>Could not open this note.</p>;
if (!doc) return <p>Opening…</p>;
return (
<button onClick={() => {
changeDoc((current: Y.Doc) => {
current.getText('content').insert(0, 'Hello ');
});
}}>
{doc.getText('content').toString()} ({acl.writers?.length ?? 0} writers)
</button>
);
}

The tuple is [document | undefined, changeDoc, aclControls, activationError]; originFilter is optionally 'all', 'remote', or 'local'. activationError is the Error from this hook’s latest failed activation of the current path (opening or creating it, or reading its readers and writers). It is undefined until an activation fails, and it is cleared when an activation succeeds or the path, the 'open' or 'create' initialization mode, or originFilter changes. A hook whose activation failed retries only after another hook instance activates the same path, so render activationError to let users leave or correct a document that cannot be activated.

changeDoc returns void. It fire-and-forgets documentRef.change() and does not return or catch its promise, so authorization, storage, encryption, or publication rejection is dropped by the wrapper. It is also a no-op before the document ref reaches the cache. Use the direct document API when the UI must await and report failures.

Each document-hook instance unsubscribes on cleanup, and the last subscriber closes and evicts that document. The open-task map deduplicates an in-flight open, including a rapid StrictMode remount. That is the only StrictMode guarantee: it is not a guarantee for swarm initialization, networking, mutations, or every side effect. A failed open or create leaves the open-task map at once, so a later mount or an initialization change starts a new activation, and a 'create' hook that joins an in-flight open of the same path creates the document only if that open fails.

The ACL controls expose addReader(user, readerKemPublicKey), which requires the recipient’s raw P-256 ECDH KEM public key, and setKemKeyPair(keyPair), which installs the local KEM key pair on the open document. Distinct-identity onboarding still needs an authenticated key exchange and a recipient flow; see Encrypted shared secrets store and Security for persistence and revocation limits.