@interop/byoe-react-template
v0.1.1
Published
Template for building 'Bring Your Own Everything' (BYOE) apps on Wallet Attached Storage: a Vite + React + TypeScript SPA with DID Auth login via a CHAPI wallet, local-first encrypted storage, and background sync, built on @interop/was-react.
Downloads
33
Readme
BYOE Notes (@interop/byoe-react-template)
A template for building "Bring Your Own Everything" (BYOE) apps on Wallet Attached Storage: a Vite + React + TypeScript SPA with DID-Auth login via a CHAPI wallet, local-first encrypted storage, and background sync, built on
@interop/was-react.
Table of Contents
- Background
- What is in this template
- Install
- Usage
- Renaming this template into a new app
- Testing
- Contribute
- License
Background
"Bring Your Own Everything" (BYOE) is a way to build web apps with no backend that the app owns. The user brings their own identity (a wallet) and their own storage (Wallet Attached Storage, WAS), and the app stores everything encrypted in that user-owned space. The app is a Relying Party: it authenticates via "Login With Wallet" (CHAPI) and reads and writes the user's WAS space using wallet-delegated authorization capabilities (zcaps). It never owns the space, never holds the wallet's root key, and invokes only the zcaps the wallet grants it.
"Bring Your Own Storage" (BYOS) is the storage half of that model. Every collection is encrypted client-side as an Encrypted Data Vault (EDV): the WAS server only ever sees opaque JWE envelopes and can neither read nor search the plaintext. Data is local-first -- a local RxDB (IndexedDB) database holds the encrypted envelopes and replicates them to WAS in the background. The app works fully offline; sync resumes on reconnect.
This template is a minimal, working BYOE app that wires all of that up through
@interop/was-react, which owns the reusable plumbing (identity derivation, the
CHAPI login flow, the session lifecycle, the encrypted local replica, WAS
replication, and the React hooks and MUI components). For the depth on those
pieces -- the login flow, session lifecycle, and sync architecture -- see the
@interop/was-react README. This document
covers what the template ships and how to make it your own.
What is in this template
- A login page (
src/pages/LoginPage.tsx) driving DID-Auth login through CHAPI: one "Login with wallet" button, a per-phase progress line, and an error alert. - A protected route: in wallet mode the library's
ProtectedRoute(from@interop/was-react/mui) gates the app; in offline dev mode a localDevGateplays the same role. - One example WAS-backed "notes" collection (
src/pages/NotesPage.tsx+src/stores/notes.ts): list, add, edit, and delete notes, read from an in-memory store hydrated from the encrypted local replica and replicated to WAS in the background. - An MUI app shell (
src/components/AppShell.tsx): a top bar with the app name, the library'sSyncStatusChip(offline / syncing / synced / error), a logout button, and theReconnectBannershown when granted access nears expiry. - A dev provisioning script (
scripts/provision-dev-grants.ts) for syncing against a local WAS server without a wallet in the loop, and a three-tier test setup (see Testing).
Install
Prerequisites
- Node.js >= 24 and pnpm (the repo pins
[email protected]). - For real background sync, a WAS server --
was-teaching-serverrun locally. - For wallet login, a CHAPI wallet -- for example
freewallet, or any CHAPI wallet reachable through the authn.io mediator.
Setup
pnpm installUsage
Auth modes
The app runs in one of two auth modes, selected by VITE_AUTH_MODE:
Wallet mode (default).
pnpm devserves the login page and gates the app behind Login With Wallet. This needs a CHAPI wallet and a WAS server to complete a login and sync.pnpm devOffline dev mode.
VITE_AUTH_MODE=dev pnpm devskips login entirely and boots straight into a local encrypted store keyed by a fixed public dev seed. No wallet, no server, no sync -- just the UI against the local replica. Good for developing screens.VITE_AUTH_MODE=dev pnpm devDev mode with sync. Dev mode plus
VITE_WAS_DEV_SYNC=truereplicates to a running WAS server without a wallet, using a locally provisioned grants file. First, with awas-teaching-serverrunning, provision the grants:pnpm run provision:devThis creates a dev Space and the app's collections on the server (default
http://localhost:3002, override withSERVER_URL) and writes the delegated zcaps to the git-ignoredpublic/dev-grants.local.json. Then run:VITE_AUTH_MODE=dev VITE_WAS_DEV_SYNC=true pnpm dev
The fixed dev seed is public. Never use dev mode (or that seed) for anything real: anything encrypted under it is readable by anyone.
Environment variables
All are optional; see src/app.config.ts for the defaults.
| Variable | Default | Purpose |
| ------------------------- | ------------------------ | ---------------------------------------------------------------------- |
| VITE_AUTH_MODE | wallet | wallet (CHAPI login gate) or dev (local dev-seed store, no login). |
| VITE_APP_ORIGIN | http://localhost:5173 | This app's origin; the CHAPI anti-phishing binding on the app key. |
| VITE_WAS_SERVER_URL | http://localhost:3002 | Remote WAS server; the expected host of every granted zcap. |
| VITE_WAS_DEV_SYNC | false | Dev mode only: replicate to WAS using a provisioned grants file. |
| VITE_WAS_DEV_GRANTS_URL | /dev-grants.local.json | Where the app fetches the dev grants JSON from. |
| VITE_WAS_SYNC_RETRY_MS | (library default) | Replication retry backoff, in ms. |
| VITE_WAS_SYNC_POLL_MS | (library default) | Periodic re-sync interval, in ms. |
Other scripts
pnpm run build # typecheck, then Vite production build
pnpm run typecheck # tsc --noEmit
pnpm run lint # eslint over src, test, scripts
pnpm run fix # eslint --fix, then prettier --writeRenaming this template into a new app
To turn "BYOE Notes" into your own app:
package.json-- changenameanddescription.src/app.config.ts-- setappConfig.appName, thecredential.credentialTypeandcredential.vocabBase(a unique credential type and vocab URI for your app's seed credential), and theCOLLECTIONSlist (see step 5).index.html-- change the<title>.UI strings -- replace the "BYOE Notes" text in
src/components/AppShell.tsxandsrc/pages/LoginPage.tsx.Replace the notes collection with your own. Each collection needs three things:
- a
{ key, id }entry inCOLLECTIONSinsrc/app.config.ts(the app-sidekeymaps to the WAS collectionid, a deliberately unprefixed, generic name shared across interoperable apps); - an entity store created with
createEntityStore<T>(key)(seesrc/stores/notes.ts); - a registry entry wiring that store's
hydrate/patch/drop/replaceAllhandlers into the exportedStoreRegistry.
Then build a page against your store's
insert/update/removeverbs, modeled onsrc/pages/NotesPage.tsx, and route to it insrc/App.tsx.Every entity payload MUST carry
updatedAt(ISO timestamp) anddeviceId(from the library'sgetDeviceId()), stamped on every insert and update: remote conflicts are resolved last-writer-wins on that pair, and a payload without them loses every conflict to the server copy.- a
Testing
The template has three browser test tiers plus a Node unit tier. The offline tier is the CI-suitable default; the WAS and wallet tiers need sibling checkouts of the supporting servers.
pnpm run test:node # Vitest + fake-indexeddb, Node (no browser)
pnpm run test:browser # Playwright, offline/mocked dev mode; CI-suitable
pnpm run test:browser:was # Playwright against a real local WAS server
pnpm run test:browser:wallet # Playwright full wallet login flow; local/manualtest:noderuns the Vitest suite againstfake-indexeddb. No browser or server needed.test:browser(the defaultplaywright.config.ts) serves the app in offline dev mode and drives the UI against the local encrypted replica only. No sibling checkouts or servers required.test:browser:was(playwright.was.config.ts) boots a localwas-teaching-serverfrom a sibling../was-teaching-servercheckout (override withWAS_SERVER_DIR), provisions dev grants, and exercises real replication in dev-sync mode.test:browser:wallet(playwright.wallet.config.ts) boots the WAS server plus afreewalletdev server (sibling../freewallet, override withFREEWALLET_DIR) and runs the full Login With Wallet flow. This is a local/manual tier, not for CI.
The combined pnpm test runs lint then test:node.
Contribute
PRs accepted. If editing this README, please conform to the
standard-readme specification.
Keep app-agnostic logic in @interop/was-react rather than growing it here.
License
MIT License (c) 2026 Interop Alliance.
