@rootherald/browser
Client ABI 6.0. Drives the TPM through the Root Herald extension and native host and returns opaque blobs. Holds no key, sees no verdict.
In development
This SDK is implemented but not yet published to its package registry. Until it ships, collect an opaque evidence blob on the device and appraise it server-side with @rootherald/node (or any available server SDK). The API shown below is the planned surface and may change before release.
terminalbash
npm install @rootherald/browserThe calls
respond(challenge, { key?, timeoutMs? })→{ evidence, key? }. Answers the relayedrhc1.string; anything else is aTypeError.keyis returned for a key ask when none was supplied; supply an existing blob to re-certify it.enroll({ enroll, activate }, { timeoutMs? })— the one-time bootstrap. The two callbacks post each leg to your backend'srelayEnroll/relayActivate.sign(key, data)→{ alg: "ES256", signature }. base64url,r||sor DER. No network.getClientStatus()/getPosture()— readiness signals: extension, host, OS support, app keys, enrolled. Never a verdict.- Errors:
ExtensionMissingError,HostMissingError,NotEnrolledError,AskUnsupportedError,AdmissionRefusedError,KeyUnloadableError,TimeoutError,AbiMismatchError. Check withinstanceof.
Example
signup.tsts
import { respond, enroll, NotEnrolledError, AdmissionRefusedError } from "@rootherald/browser";
const post = (url: string, body: unknown) =>
fetch(url, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) })
.then((r) => (r.ok ? r.json() : Promise.reject(new Error(String(r.status)))));
const { challengeId, challenge } = await post("/api/challenge", {});
let evidence;
try {
({ evidence } = await respond(challenge)); // no prompt on an enrolled device
} catch (err) {
if (!(err instanceof NotEnrolledError)) throw err;
try {
await enroll({
enroll: (blob) => post("/api/enroll", { enrollRequestBlob: blob, challengeId }),
activate: (blob) => post("/api/activate", { activationResponse: blob }),
});
} catch (e) {
if (e instanceof AdmissionRefusedError) throw new Error("device not eligible"); // no prompt was shown
throw e;
}
({ evidence } = await respond(challenge));
}
await post("/api/signup", { challengeId, evidence, ...form }); // your backend: rh.verify()