# JavaScript Encrypted Data Vault Client _(@interop/edv-client)_

[![Node.js CI](https://github.com/interop-alliance/edv-client/workflows/CI/badge.svg)](https://github.com/interop-alliance/edv-client/actions?query=workflow%3A%22CI%22)
[![NPM Version](https://img.shields.io/npm/v/@interop/edv-client.svg)](https://npm.im/@interop/edv-client)

> A JavaScript library for Web and node.js apps for interfacing with a remote
> Encrypted Data Vault server

## Table of Contents

- [Background](#background)
- [Install](#install)
- [Usage](#usage)
- [API](#api)
- [Contribute](#contribute)
- [License](#license)

## Background

This library provides a client that Web and node.js apps can use to interface
with remote Encrypted Data Vault (EDV) servers.

It consists of one main class:

- **`EdvClient`** - instances provide a CRUD (+ find) interface to a specific
  configured Encrypted Data Vault server and ensure appropriate database indexes
  are set up. Static methods allow for the creation of EDVs with a remote
  storage service, e.g. Encrypted Data Vault storage servers.

#### Encrypted Data Vault storage servers:

- MongoDB-based - https://github.com/digitalbazaar/bedrock-edv-storage
- PouchDB-based - https://github.com/digitalbazaar/bedrock-web-pouch-edv

## Install

- Browsers and Node.js 24+ are supported.
- [Streams API][] required. Older browsers must use a polyfill.
- [Web Crypto API][] required. Older browsers must use a polyfill.

To install from NPM:

```sh
npm install @interop/edv-client
```

To install locally (for development):

```sh
git clone https://github.com/interop-alliance/edv-client.git
cd edv-client
pnpm install
```

This library is written in TypeScript and built with `tsc`. Common scripts:

```sh
pnpm run build         # compile src/ to dist/
pnpm run lint          # eslint
pnpm run test:node     # vitest (Node)
pnpm run test:browser  # playwright (browser smoke test)
```

### Entry points

The package publishes two:

- `@interop/edv-client` -- everything: `EdvClient`, `EdvDocument`,
  `HttpsTransport`, plus the core entry's exports.
- `@interop/edv-client/core` -- `EdvClientCore`, `EdvDocumentCipher`,
  `assertDocId` and the abstract `Transport`, and nothing that talks HTTP.
  Nothing reachable from it imports `@interop/http-client` or
  `@interop/http-signature-zcap-invoke`. Import from here when you bring your
  own `Transport` subclass, or when you only encrypt and decrypt locally.

```js
import { EdvClientCore, Transport } from '@interop/edv-client/core'
```

### React Native

This library is isomorphic and runs on React Native, with one environment
requirement: it uses the Web Crypto `crypto.getRandomValues()` API to generate
random identifiers, which React Native does not provide natively. Consumers must
install the [`react-native-get-random-values`][] polyfill and import it
**once**, before any `edv-client` code runs (typically at the very top of your
app entry, e.g. `index.js`):

```sh
npm install react-native-get-random-values
```

```js
// must be the first import in your app entry
import 'react-native-get-random-values'
```

It is declared as an optional `peerDependency`. No additional shim is needed for
hashing -- SHA-256 is provided by the pure-JS [`@noble/hashes`][]
implementation.

## Usage

### Creating and registering an Encrypted Data Vault (EDV)

First, create a key agreement key and an HMAC (hash-based message authentication
code) key for encrypting your documents and blinding any indexed attributes in
them. This requires creating some cryptographic key material which can be done
locally or via a KMS system. The current example shows using a KMS system (TODO:
show a simpler local example):

```js
import { CapabilityAgent, KeystoreAgent, KmsClient } from 'webkms-client'
import { EdvClient } from '@interop/edv-client'
```

Although Encrypted Data Vaults are not bound to any particular key management
system, we recommend that you set up a Key Management Service using an
implementation such as
[`webkms-switch`](https://github.com/digitalbazaar/webkms-switch) which you can
connect to using
[`webkms-client`](https://github.com/digitalbazaar/webkms-client).

Optional:

```js
// create a CapabilityAgent (for invoking zcaps)
const capabilityAgent = await CapabilityAgent.fromSecret({ secret, handle })

// create a keystore and an agent for working with it
// the baseUrl can be set to a dev API or production API
const kmsBaseUrl = `${config.server.baseUri}/kms`
const keystore = KmsClient.createKeystore({
  // the url for the keystore is configurable
  url: `${kmsBaseUrl}/keystores`,
  config: {
    // on init the sequence must be 0
    sequence: 0,
    controller: capabilityAgent.id,
    invoker: capabilityAgent.id,
    // this allows the capabilityAgent to delegate zCaps
    delegator: capabilityAgent.id
  },
  /**
   * optional `httpsAgent`,
   * usually not applicable for front-end (you may use axios),
   * for back-end use cases a nodejs `https.Agent`
   * may be used to allow the use of self signed certificates using
   * the `rejectUnauthorized: false` flag in the contructor.
   */
  httpsAgent
})
const keystoreAgent = new KeystoreAgent({ keystore, capabilityAgent })

// use the keystore agent to create key agreement and HMAC keys
const keyAgreementKey = await keystoreAgent.generateKey({
  type: 'keyAgreement'
})
const hmac = await keystoreAgent.generateKey({ type: 'hmac' })
```

Now you can create and register a new EDV configuration:

```js
// TODO: explain EDV service must be able to authenticate user
const controller = 'account id goes here'

const config = {
  // on init the sequence must be 0 and is required
  sequence: 0,
  controller,
  // TODO: Explain what 'referenceId' is
  referenceId: 'primary',
  keyAgreementKey: { id: keyAgreementKey.id, type: keyAgreementKey.type },
  hmac: { id: hmac.id, type: hmac.type }
}

// sends a POST request to the remote service to create an EDV
const remoteConfig = await EdvClient.createEdv({ config })

// connect to the new EDV via a `EdvClient`
const client = new EdvClient({ id: remoteConfig.id, keyAgreementKey, hmac })
// to only use fips-compliant key agreement and encryption algorithms:
const client = new EdvClient({
  id: remoteConfig.id,
  keyAgreementKey,
  hmac,
  cipherVersion: 'fips'
})
```

You can specify a url when you create and register a new EDV configuration:

```js
// TODO: explain EDV service must be able to authenticate user
const controller = 'account id (or DID if using zcaps) goes here'

const config = {
  // on init the sequence must be 0 and is required
  sequence: 0,
  controller,
  // TODO: Explain what 'referenceId' is
  referenceId: 'primary',
  keyAgreementKey: { id: keyAgreementKey.id, type: keyAgreementKey.type },
  hmac: { id: hmac.id, type: hmac.type }
}

// sends a POST request to the remote service to create an EDV
const remoteConfig = await EdvClient.createEdv({
  url: 'https://server.example/edvs',
  config
  // must pass `invocationSigner` and optional `capability` if `controller`
  // is a DID
  /* invocationSigner, capability */
})

// connect to the new EDV via a `EdvClient`
const client = new EdvClient({ id: remoteConfig.id, keyAgreementKey, hmac })
```

### Loading a saved EDV config

If you have previously registered an EDV config (via `createEdv()`), and you
know its `id`, you can fetch its config via `get()`:

```js
// registered config
const { id } = await EdvClient.createEdv({ config })

// later, it can be fetched via the id
const remoteConfig = await EdvClient.getConfig({ id })

// connect to the existing EDV via an `EdvClient` instance
const client = new EdvClient({ id: remoteConfig.id, keyAgreementKey, hmac })
```

If you know a controller/`accountId` but do not know a specific EDV `id`, you
can create a client for an EDV by a controller-scoped custom `referenceId`:

```js
// get the account's 'primary' EDV config to connect to the EDV
// note that a referenceId can be any string but must be unique per controller
const config = await EdvClient.findConfig({
  controller: accountId,
  referenceId: 'primary'
})
const client = new EdvClient({ id: config.id, keyAgreementKey, hmac })
```

### Using a EdvClient instance for document storage

See the API section below.

## API

### `EdvClient`

#### `constructor`

#### `insert`

#### `get`

#### `update`

#### `delete`

#### `find`

#### `ensureIndex`

#### `updateIndex`

## Contribute

Please follow the existing code style. See [CONTRIBUTING.md](CONTRIBUTING.md)
for code style and contribution conventions, and
[ARCHITECTURE.md](ARCHITECTURE.md) for the module map and EDV server protocol
contract.

PRs accepted.

If editing the Readme, please conform to the
[standard-readme](https://github.com/RichardLitt/standard-readme) specification.

## License

[BSD-3-Clause](LICENSE) Copyright 2019-2025 Digital Bazaar, Inc.

Commercial support is available by contacting
[Digital Bazaar](https://digitalbazaar.com/) <support@digitalbazaar.com>.

[`@noble/hashes`]: https://github.com/paulmillr/noble-hashes
[`react-native-get-random-values`]:
  https://github.com/LinusU/react-native-get-random-values
[Streams API]: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API
[Web Crypto API]:
  https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API
