Decentralized Web Node (DWN) SDK
Introduction
This repository contains a reference implementation of Decentralized Web Node (DWN) as per the specification. This specification is in a draft state and very much so a WIP. For the foreseeable future, a lot of the work on DWN will be split across this repo and the repo that houses the specification. The current implementation does not include all interfaces described in the DWN spec, but is enough to begin building test applications.
This project is used as a dependency by several other projects.
Proposals and issues for the specification itself should be submitted as pull requests to the spec repo.
Running online environment
Interested in contributing instantly? You can make your updates directly without cloning in the running CodeSandbox environment.
Installation
If you are interested in using DWNs and web5 in your web app, you probably want to look at web5-js, instead of this repository. Head on over here: https://github.com/TBD54566975/web5-js.
For advanced users wishing to use this repo directly:
npm install @tbd54566975/dwn-sdk-js
Additional Steps
This package has dependency on @noble/ed25519
and @noble/secp256k1
v2, additional steps are needed for some environments:
Node.js <= 18
import { webcrypto } from "node:crypto";
if (!globalThis.crypto) globalThis.crypto = webcrypto;
React Native
Usage of DWN SDK in react native requires a bit of set up at the moment. To simplify, we've published an npm package that can be used to set everything up. Follow the instructions to get started.
Usage in Browser:
dwn-sdk-js
requires 2 polyfills: crypto
and stream
. we recommend using crypto-browserify
and stream-browserify
. Both of these polyfills can be installed using npm. e.g. npm install --save crypto-browserify stream-browserify
Vanilla HTML / JS
DWN SDK includes a polyfilled distribution that can imported in a module
script tag. e.g.
<!DOCTYPE html>
<html lang="en">
<body>
<script type="module">
import { Dwn, DataStream, DidKeyResolver, Jws, RecordsWrite } from 'https://cdn.jsdelivr.net/npm/@tbd54566975/dwn-sdk-js@0.1.1/dist/bundles/dwn.js'
import { MessageStoreLevel, DataStoreLevel, EventLogLevel } from 'https://cdn.jsdelivr.net/npm/@tbd54566975/dwn-sdk-js@0.1.1/dist/bundles/level-stores.js'
const messageStore = new MessageStoreLevel();
const dataStore = new DataStoreLevel();
const eventLog = new EventLogLevel();
const dwn = await Dwn.create({ messageStore, dataStore, eventLog });
const didKey = await TestDataGenerator.generateDidKeyPersona();
const encoder = new TextEncoder();
const data = encoder.encode('Hello, World!');
const recordsWrite = await RecordsWrite.create({
data,
dataFormat: 'application/json',
published: true,
schema: 'yeeter/post',
signer: Jws.createSigner(didKey)
});
const dataStream = DataStream.fromBytes(data);
const result = await dwn.processMessage(didKey.did, recordsWrite.message, { dataStream });
console.log(result.status);
console.assert(result.status.code === 202)
await dwn.close()
</script>
</body>
</html>
Webpack >= 5
Add the following to the top level of your webpack config (webpack.config.js
)
resolve: {
fallback: {
stream: require.resolve("stream-browserify"),
crypto: require.resolve("crypto-browserify")
}
}
Vite
Add the following to the top level of your vite config (vite.config.js
)
define: {
global: 'globalThis'
},
resolve: {
alias: {
'crypto': 'crypto-browserify',
'stream': 'stream-browserify'
}
}
esbuild
We recommend using node-stdlib-browser
instead of crypto-browserify
and stream-browserify
individually. Example usage:
import esbuild from 'esbuild'
import stdLibBrowser from 'node-stdlib-browser'
import polyfillProviderPlugin from 'node-stdlib-browser/helpers/esbuild/plugin'
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
esbuild.build({
entryPoints: ['dwn-sdk-test.js'],
platform: 'browser',
bundle: true,
format: 'esm',
outfile: 'dist/dwn-sdk-test.js',
inject : [require.resolve('node-stdlib-browser/helpers/esbuild/shim')],
plugins : [polyfillProviderPlugin(stdLibBrowser)],
define : {
'global': 'globalThis'
}
})
Usage
API docs
import { Dwn, DataStream, DidKeyResolver, Jws, RecordsWrite } from '@tbd54566975/dwn-sdk-js';
import { DataStoreLevel, EventLogLevel, MessageStoreLevel } from '@tbd54566975/dwn-sdk-js/stores';
const messageStore = new MessageStoreLevel();
const dataStore = new DataStoreLevel();
const eventLog = new EventLogLevel();
const dwn = await Dwn.create({ messageStore, dataStore, eventLog });
const didKey = await TestDataGenerator.generateDidKeyPersona();
const encoder = new TextEncoder();
const data = encoder.encode('Hello, World!');
const recordsWrite = await RecordsWrite.create({
data,
dataFormat: 'application/json',
published: true,
schema: 'yeeter/post',
signer: Jws.createSigner(didKey)
});
const dataStream = DataStream.fromBytes(data);
const result = await dwn.processMessage(didKey.did, recordsWrite.message, { dataStream });
console.log(result.status);
With a web wallet installed:
const result = await window.web5.dwn.processMessage({
method: "RecordsQuery",
message: {
filter: {
schema: "http://some-schema-registry.org/todo",
},
dateSort: "createdAscending",
},
});
Custom Tenant Gating
By default, all DIDs are allowed as tenants. A custom tenant gate implementation can be provided when initializing the DWN.
import { ActiveTenantCheckResult, Dwn, TenantGate, DataStoreLevel, EventLogLevel, MessageStoreLevel } from '@tbd54566975/dwn-sdk-js';
class CustomTenantGate implements TenantGate {
public async isActiveTenant(did): Promise<ActiveTenantCheckResult> {
}
}
const messageStore = new MessageStoreLevel();
const dataStore = new DataStoreLevel();
const eventLog = new EventLogLevel();
const tenantGate = new CustomTenantGate();
const dwn = await Dwn.create({ messageStore, dataStore, eventLog, tenantGate });
Custom Signature Signer
If you have the private key readily available, it is recommended to use the built-in PrivateKeySigner
. Otherwise, you can implement a customer signer to interface with external signing service, API, HSM, TPM etc and use it for signing your DWN messages:
class CustomSigner implements Signer {
public keyId = 'did:example:alice#key1';
public algorithm = 'EdDSA';
https:
public async sign (content: Uint8Array): Promise<Uint8Array> {
...
}
}
const signer = new CustomSigner();
const options: RecordsWriteOptions = {
...
signer
};
const recordsWrite = await RecordsWrite.create(options);
Release/Build Process
The DWN JS SDK releases builds to npmjs.com. There are two build types: stable build and unstable build.
Stable Build
This is triggered manually by:
- Increment
version
in package.json
in Semantic Versioning (semver) format. - Merge the change into
main
branch - Create a release from GitHub.
An official build with version matching the package.json
will be published to npmjs.com.
Unstable Build
Every push to the main
branch will automatically trigger an unstable build to npmjs.com for developers to experiment and test.
The version string contains the date as well as the commit hash of the last change.
An example version string:
0.0.26-unstable-2023-03-16-36ec2ce
0.0.26
came from version
in package.json
2023-03-16
indicates the date of March 16th 202336ec2ce
is the commit hash of the last change
Some projects that use this library:
Architecture
NOTE: The diagram is a conceptual view of the architecture, the actual component abstraction and names in source file may differ.
Project Resources