Skip to content
SDKs · ReactComing soonView source

@rootherald/react

A small React wrapper around @rootherald/browser. Keyless: it collects a hardware-rooted evidence blob over a backend-issued nonce and hands it to your callback — it holds no secret and never renders a verdict. Your backend relays the blob to the verify API, which appraises it.

Coming soon — in development

This SDK is planned and not yet available. Until it ships, mint attestation tokens on the device and verify them server-side with @rootherald/node (or any available server SDK). The API shown below is the planned surface and may change before release.

Install

npmbash
npm install @rootherald/react @rootherald/browser

Provider setup

Wrap your app once. The provider configures the underlying keyless browser collector and exposes hooks to the rest of your tree. The publishable key is for site/origin attribution only — never a secret.

app/providers.tsxtsx
"use client";

import { RootHeraldProvider } from "@rootherald/react";

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <RootHeraldProvider siteKey="rh_pk_live_REPLACE_ME">
      {children}
    </RootHeraldProvider>
  );
}

<RootHeraldGate> — the drop-in form gate

The gate collects an evidence blob over a nonce your backend issued, then hands the blob to your onEvidence callback. The nonce comes from your backend (which called POST /api/v1/attestations/challenge with its rh_sk_ secret), and your backend relays the blob to POST /api/v1/attestations/verify and enforces the verdict. The verdict never travels through the client.

components/SignupForm.tsxtsx
import { RootHeraldGate } from "@rootherald/react";

export function SignupForm() {
  return (
    <RootHeraldGate
      // getNonce hits YOUR backend, which owns the rh_sk_ secret and the verdict.
      getNonce={() => myBackend.getChallengeNonce("signup")}
      onEvidence={async (evidence) => {
        // Relay the opaque blob to your backend, which POSTs it to
        // /api/v1/attestations/verify and enforces the verdict.
        await fetch("/api/signup", {
          method: "POST",
          body: JSON.stringify({ evidence, ...formState }),
        });
      }}
      onDeclined={() => alert("Please complete device verification to sign up.")}
    >
      {({ loading, ready, collect }) => (
        <button onClick={collect} disabled={!ready || loading}>
          {loading ? "Collecting evidence…" : "Create account"}
        </button>
      )}
    </RootHeraldGate>
  );
}
Render-prop API

We use a render-prop instead of a wrapped button so you keep full control of the button's markup and your design system. ready is false until the collector has detected the platform capability; loading is true while evidence collection is in flight.

Hooks

  • useRootHerald() returns { collect, isAvailable }. collect(nonce) walks the keyless @rootherald/browser collector and resolves to the opaque evidence blob for you to relay — it never returns a token, deviceId, or verdict.
  • useClientStatus()returns the collector's local readiness (enrolled / not enrolled / unsupported) so you can gate UI. It reports no device class or verdict — those live only in the verify response on your backend.