Home

Awesome

<!-- @format -->

Decentralized Web Node (DWN) SDK <!-- omit in toc -->

NPM codecov Build Status License Chat

πŸŽ‰ Hacktoberfest 2024 πŸŽ‰

dwn-sdk-js is a participating project in Hacktoberfest 2024! We’re so excited for your contributions, and have created a wide variety of issues so that anyone can contribute. Whether you're a seasoned developer or a first-time open source contributor, there's something for everyone.

To get started:

  1. Read the contributing guide.
  2. Read the code of conduct.
  3. Choose a task from this project's Hacktoberfest issues in our Project Hub and follow the instructions. Each issue has the 🏷️ hacktoberfest label.

Have questions? Connecting with us in our Discord community in the #hacktoberfest project channel.


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.

Button to click to edit code in CodeSandbox

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

// node.js 18 and earlier,  needs globalThis.crypto polyfill
import { webcrypto } from "node:crypto";
// @ts-ignore
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 necessary modules from external sources using ES6 modules.
    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'

    // Create instances of various components from the imported modules.
    const messageStore = new MessageStoreLevel();
    const dataStore = new DataStoreLevel();
    const eventLog = new EventLogLevel();
    const dwn = await Dwn.create({ messageStore, dataStore, eventLog });

    // Generate a did:key DID (Decentralized Identifier).
    const didKey = await TestDataGenerator.generateDidKeyPersona();

    // Create some data to be stored.
    const encoder = new TextEncoder();
    const data = encoder.encode('Hello, World!');

    // Create a RecordsWrite message to be stored in DWN.
    const recordsWrite = await RecordsWrite.create({
      data,
      dataFormat: 'application/json',
      published: true,
      schema: 'yeeter/post',  // Specify a schema for the data.
      signer: Jws.createSigner(didKey) // Sign the data using the generated DID key.
    });

    // Create a readable stream from the data to be stored.
    const dataStream = DataStream.fromBytes(data);
    // Process the RecordsWrite message using the DWN instance.
    const result = await dwn.processMessage(didKey.did, recordsWrite.message, { dataStream });

    // Log the processing result status and perform an assertion.
    console.log(result.status);
    console.assert(result.status.code === 202)

    // Close the DWN instance, freeing up resources.
    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);

// Build the project using esbuild.
esbuild.build({
  entryPoints: ['dwn-sdk-test.js'],
  platform: 'browser',
  bundle: true,
  format: 'esm',
  outfile: 'dist/dwn-sdk-test.js',

  // Inject the specified shim for Node.js standard library browser compatibility.
  inject      : [require.resolve('node-stdlib-browser/helpers/esbuild/shim')],
  // Use the polyfillProviderPlugin to provide polyfills for Node.js standard library.
  plugins     : [polyfillProviderPlugin(stdLibBrowser)],
  // Define 'global' as 'globalThis' to ensure compatibility with global objects.
  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';

// Initialize the required stores and components for the DWN SDK.
const messageStore = new MessageStoreLevel();
const dataStore = new DataStoreLevel();
const eventLog = new EventLogLevel();
const dwn = await Dwn.create({ messageStore, dataStore, eventLog });

// Generate a did:key DID (Decentralized Identifier).
const didKey = await TestDataGenerator.generateDidKeyPersona();

// Create some data to be stored.
const encoder = new TextEncoder();
const data = encoder.encode('Hello, World!');

// Create a RecordsWrite message to be stored in the DWN.
const recordsWrite = await RecordsWrite.create({
  data,
  dataFormat: 'application/json',
  published: true, // Mark the data as published.
  schema: 'yeeter/post', // Specify a schema for the data.
  signer: Jws.createSigner(didKey) // Sign the data using the generated DID key.
});

// Create a readable stream from the data to be stored.
const dataStream = DataStream.fromBytes(data);
// Process the RecordsWrite message using the DWN instance.
const result = await dwn.processMessage(didKey.did, recordsWrite.message, { dataStream });
// Log the processing result status.
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';

// Define a custom implementation of the TenantGate interface.
class CustomTenantGate implements TenantGate {
  public async isActiveTenant(did): Promise<ActiveTenantCheckResult> {
    // Custom implementation
  }
}

// Initialize the required stores and components for the DWN SDK.
const messageStore = new MessageStoreLevel();
const dataStore = new DataStoreLevel();
const eventLog = new EventLogLevel();
// Create an instance of the custom TenantGate.
const tenantGate = new CustomTenantGate();
// Create a DWN instance with configured stores, logs, and the custom TenantGate.
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:

// Create a custom signer implementing the Signer interface.
class CustomSigner implements Signer {
  public keyId = 'did:example:alice#key1'; // Specify the key ID.
  public algorithm = 'EdDSA'; // Specify the signing algorithm (valid `alg` value published).
  https://www.iana.org/assignments/jose/jose.xhtml
  public async sign (content: Uint8Array): Promise<Uint8Array> {
    ... // custom signing logic
  }
}

// Create an instance of the custom signer for authorization.
const signer = new CustomSigner();

// Define options for creating a RecordsWrite message.
const options: RecordsWriteOptions = {
  ...
  signer // Use the custom signer for authorization.
};

// Create a RecordsWrite message with the specified options.
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:

  1. Increment version in package.json in Semantic Versioning (semver) format.
  2. Merge the change into main branch
  3. 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

Some projects that use this library:

Architecture

<img src="./images/dwn-architecture.png" alt="Architecture diagram of DWN SDN" width="700">

NOTE: The diagram is a conceptual view of the architecture, the actual component abstraction and names in source file may differ.

Project Resources

ResourceDescription
CODEOWNERSOutlines the project lead(s)
CODE_OF_CONDUCT.mdExpected behavior for project contributors, promoting a welcoming environment
CONTRIBUTING.mdDeveloper guide to build, test, run, access CI, chat, discuss, file issues
GOVERNANCE.mdProject governance
LICENSEApache License, Version 2.0
Q_AND_A.mdQuestions and answers on DWN