diff --git a/examples/simple-agent/.gitignore b/examples/simple-agent/.gitignore index 0467ea0eb..9cda4e2f9 100644 --- a/examples/simple-agent/.gitignore +++ b/examples/simple-agent/.gitignore @@ -1,5 +1,4 @@ -# Agent Specific - -BLOCKSTORE/ -DATASTORE/ -INDEX/ \ No newline at end of file +DATASTORE-* +EVENTLOG-* +INDEX-* +MESSAGESTORE-* \ No newline at end of file diff --git a/examples/simple-agent/etc/did.json b/examples/simple-agent/etc/did.json index b3ddb677d..d18a28ca7 100644 --- a/examples/simple-agent/etc/did.json +++ b/examples/simple-agent/etc/did.json @@ -7,7 +7,7 @@ "content": { "publicKeys": [ { - "id": "key-1", + "id": "dwn", "type": "JsonWebKey2020", "purposes": [ "authentication" @@ -68,9 +68,9 @@ ], "keys": [ { - "id": "key-1", + "id": "dwn", "type": "JsonWebKey2020", - "keypair": { + "keyPair": { "publicJwk": { "kty": "EC", "crv": "secp256k1", diff --git a/examples/simple-agent/package.json b/examples/simple-agent/package.json index 7f4055f1e..581a17e32 100644 --- a/examples/simple-agent/package.json +++ b/examples/simple-agent/package.json @@ -16,7 +16,7 @@ "dependencies": { "@koa/cors": "4.0.0", "@koa/router": "12.0.0", - "@tbd54566975/web5": "0.3.1-unstable.e2dfe57-2023.3.23-19-07-37", + "@tbd54566975/web5": "0.5.0", "koa": "2.14.1", "koa-body": "6.0.1", "mkdirp": "2.1.5", diff --git a/examples/simple-agent/src/index.js b/examples/simple-agent/src/index.js index 33a72f75f..c148bb011 100644 --- a/examples/simple-agent/src/index.js +++ b/examples/simple-agent/src/index.js @@ -20,10 +20,16 @@ router.post('/dwn', async (ctx, _next) => { try { const response = await receiveHttp(ctx); - // Normalize DWN MessageReply and HTTP Reponse - ctx.status = response?.status?.code ?? response?.status; - ctx.statusText = response?.status?.detail ?? response?.statusText; - ctx.body = 'entries' in response ? { entries: response.entries } : response.body; + console.log('SIMPLE AGENT receiveHTTP response:', response); + + // // All DWN MessageReply responses contain a `status` object. + // // DWN RecordsQuery responses contain an `entries` array of query results. + // // DWN RecordsRead responses contain a data property which is a Readable stream. + // const { message, ...retainedResponse } = response; + + // ctx.body = retainedResponse; + // ctx.status = retainedResponse?.status?.code; + // ctx.statusText = retainedResponse?.status?.detail; } catch(err) { console.error(err); diff --git a/examples/simple-agent/src/utils.js b/examples/simple-agent/src/utils.js index 27784bc2f..d2782cfc5 100644 --- a/examples/simple-agent/src/utils.js +++ b/examples/simple-agent/src/utils.js @@ -1,10 +1,23 @@ import getRawBody from 'raw-body'; +import { DataStoreLevel, Dwn, EventLogLevel, MessageStoreLevel } from '@tbd54566975/dwn-sdk-js'; import { Web5 } from '@tbd54566975/web5'; import fs from 'node:fs'; import mkdirp from 'mkdirp'; import { createRequire } from 'node:module'; -const web5 = new Web5(); +// Use custom names for the block, message, and data stores to make it possible to launch multiple simple agents +// in the same directory. If you don't do this, you will get LevelDB lock errors. +const port = await getPort(process.argv); +const dataStore = new DataStoreLevel({ blockstoreLocation: `DATASTORE-${port}` }); +const eventLog = new EventLogLevel({ location: `EVENTLOG-${port}` }); +const messageStore = new MessageStoreLevel({ + blockstoreLocation : `MESSAGESTORE-${port}`, + indexLocation : `INDEX-${port}`, +}); + +const dwnNode = await Dwn.create({ dataStore, eventLog, messageStore }); + +export const web5 = new Web5({ dwn: { node: dwnNode }}); const etcPath = './etc'; const didStoragePath = `${etcPath}/did.json`; @@ -14,7 +27,7 @@ const require = createRequire(import.meta.url); const testProtocol = require('../resources/test-protocol.json'); const protocols = [testProtocol]; -async function getPort(processArgv) { +export async function getPort(processArgv) { const defaultPort = 8080; // Find the -p option and get the port number @@ -27,7 +40,7 @@ async function getPort(processArgv) { return port; } -async function loadConfig() { +export async function loadConfig() { if (!fs.existsSync(etcPath)) { // ensure that directory for persistent storage exists mkdirp.sync(etcPath); @@ -42,15 +55,14 @@ async function loadConfig() { didState = await initOperatorDid(); fs.writeFileSync(didStoragePath, JSON.stringify(didState, null, 2)); } - web5.did.register({ + web5.did.manager.set(didState.id, { connected: true, - did: didState.id, endpoint: 'app://dwn', - keys: didState.keys[0].keypair, + keys: { ['#dwn']: { keyPair: didState.keys[0].keyPair} }, }); } -async function initOperatorDid() { +export async function initOperatorDid() { const operatorDid = await web5.did.create('ion', { services: [ { @@ -65,7 +77,7 @@ async function initOperatorDid() { return operatorDid; } -async function initializeProtocols() { +export async function initializeProtocols() { for (let { protocol, definition } of protocols) { const queryResponse = await web5.dwn.protocols.query(didState.id, { author: didState.id, @@ -87,7 +99,7 @@ async function initializeProtocols() { } } -async function receiveHttp(ctx) { +export async function receiveHttp(ctx) { const encodedMessage = ctx.get(web5.transports.http.ENCODED_MESSAGE_HEADER); if (!encodedMessage) throw 'Message is missing or malformed'; @@ -99,20 +111,9 @@ async function receiveHttp(ctx) { const data = await getRawBody(ctx.req); - console.log('TRACE - receiveHttp() - message:', message); - return await web5.send(target, { author, data, message, }); } - -export { - getPort, - initializeProtocols, - initOperatorDid, - loadConfig, - receiveHttp, - web5, -}; diff --git a/examples/test-dashboard/desktop-agent-original.html b/examples/test-dashboard/desktop-agent-original.html new file mode 100644 index 000000000..df1527f12 --- /dev/null +++ b/examples/test-dashboard/desktop-agent-original.html @@ -0,0 +1,937 @@ + + + + + + + Agent Test Dashboard + + + + + + + +
+
+ DID Connect +
+ + +
+ + +
+
+ +
+

Write Records

+ +
+
+ + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + +
+
+ +
+ +

Query Records

+ +
+
+ + + +
+
+ +
+
+ + + +
+
+ +
+
+ + + +
+
+ +
+ +

Read Records

+ +
+
+ +
+
+ +
+
+ + + +
+
+ +
+ +

Delete Records

+ +
+
+ + + +
+
+ +
+ +

Protocols

+ +
+
+
+ + + +
+
+ +
+ + + + + + +
+
+ +
+ +

Custom Data Formats

+ +
+
+ + + + + + + + + +
+
+ +
+
+ + + + + + + +
+
+ +
+ +

New Tests

+ +
+
+ Write data authored by Alice's DID to Alice's DWN WITH local key chain + +
+
+ +
+
+ Write data authored by Alice's DID to Alice's DWN withOUT local key chain + +
+
+ +
+
+ Write data authored by Alice's DID to Bob's DWN WITH local key chain + +
+
+ +
+
+ Write data authored by Alice's DID to Bob's DWN withOUT local key chain + +
+
+ +
+
+ Query data authored by Alice's DID to Bob's DWN withOUT local key chain + +
+
+ + +
+ + + + + \ No newline at end of file diff --git a/examples/test-dashboard/desktop-agent.html b/examples/test-dashboard/desktop-agent.html index 3b2830875..ce4aed003 100644 --- a/examples/test-dashboard/desktop-agent.html +++ b/examples/test-dashboard/desktop-agent.html @@ -23,6 +23,7 @@ font-family: 'IBM Plex Mono'; padding: 0; margin: 0; + max-width: 72rem; } input, button { @@ -31,50 +32,35 @@ main { color: rgb(250, 250, 250); - padding: 0rem 1rem; } - .box { - max-width: 52rem; - display: flex; - flex-direction: column; - } - - .box>.row { - display: flex; - align-items: start; - padding-top: 0.5em; - } - - .box>.row>label { - padding-right: 0.5em; - padding-left: 0.5em; + fieldset { + border-width: 2px; + border-style: solid; + border-radius: 5px; + padding: 1.25rem; + padding-block-start: 0.5em; + margin: 0rem 1rem 2rem 1rem; } - - .box>.row>button { - margin-left: auto; + + fieldset.yellow { + border-color: var(--color-yellow); } - .box>.row>input[type=text] { - width: 8em; + fieldset.yellow>legend { + color: var(--color-yellow); } - - .box>.row>textarea { - width: 250px; - height: 125px; + + fieldset.blue { + border-color: var(--color-blue); } - fieldset { - border: 2px solid var(--color-yellow); - border-radius: 5px; - padding: 20px; - margin: 0rem 1rem; + fieldset.blue>legend { + color: var(--color-blue); } legend { - color: var(--color-yellow); font-size: 1.5rem; - padding: 0 10px; } fieldset .buttons { @@ -90,12 +76,13 @@ color: white; cursor: pointer; font-size: 14px; + min-width:fit-content; + white-space: nowrap; padding: 0.5em 20px; text-align: center; } button:hover { - /* background-color: #45a049; */ filter: brightness(95%); } @@ -104,6 +91,20 @@ font-size: 1rem; margin-top: 1rem; text-align: center; + + /* for smooth appearance transition */ + opacity: 0; + max-height: 0; + overflow: hidden; + margin: 0; + padding: 0; + transition: opacity 0.4s ease, max-height 0.4s ease, margin 0.4s ease, padding 0.4s ease; + } + + #container_connect_status.visible { + opacity: 1; + max-height: 1000px; + margin-top: 1rem; } #container_connect_status.success { @@ -117,6 +118,20 @@ #container_security_code { margin-top: 1rem; text-align: center; + + /* for smooth appearance transition */ + opacity: 0; + max-height: 0; + overflow: hidden; + margin: 0; + padding: 0; + transition: opacity 0.4s ease, max-height 0.4s ease, margin 0.4s ease, padding 0.4s ease; + } + + #container_security_code.visible { + opacity: 1; + max-height: 1000px; + margin-top: 1rem; } #container_security_code .label { @@ -143,19 +158,75 @@ background-color: var(--color-blue-10); line-height: 2.5rem; /* Vertically center the text */ } + + main #output { + padding: 0 1rem; + } + + main #output .row { + background-color: rgb(51, 51, 51); + border: none; + border-radius: 0.5rem; + font-size: 0.75rem; + margin: 1rem 0rem; + padding: 1rem; + white-space: pre-wrap; + } + + main #output .error { + color: var(--color-red); + } + + main .button-container { + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-auto-rows: 2rem; + column-gap: 1rem; + row-gap: 1rem; + width: 100%; + min-width: max-content; + /* max-width: 37.5rem; */ + margin: 0 auto; + box-sizing: border-box; + } + + @media (max-width: 27em) { + main .button-container { + grid-template-columns: repeat(2, 1fr); + } + } + + main .output-status { + display: flex; + justify-content: space-between; + align-items: center; + padding-left: 1rem; + padding-right: 1rem; + padding-top: 1rem; + } + + main .output-status .btn { + background-color: var(--color-red); + filter: brightness(70%); + } + + main .output-status .btn:hover { + background-color: var(--color-red); + filter: brightness(95%); + }
-
+
DID Connect
- -
-

Write Records

- -
-
- - - - - - -
- -
- - - - - - -
- -
- - - - - - -
-
- -
- -

Query Records

- -
-
- - - -
-
- -
-
- - - -
-
- -
-
- - - -
-
- -
- -

Read Records

- -
-
- -
-
- -
-
- - - -
-
- -
- -

Delete Records

- -
-
- - - -
-
- -
- -

Protocols

- -
-
-
- - - -
-
- -
- - - - - - +
+ Records Functions +
+ + + + + + + + + + + + + + + + + + +
-
- -
- -

Custom Data Formats

- -
-
- - - - - - - - - -
-
- -
-
- - - - - - - -
-
- -
- -

New Tests

- -
-
- Write data authored by Alice's DID to Alice's DWN WITH local key chain - -
-
- -
-
- Write data authored by Alice's DID to Alice's DWN withOUT local key chain - -
-
- -
-
- Write data authored by Alice's DID to Bob's DWN WITH local key chain - -
-
- -
-
- Write data authored by Alice's DID to Bob's DWN withOUT local key chain - + + +
+ Record Instance +
+ + + +
-
- -
-
- Query data authored by Alice's DID to Bob's DWN withOUT local key chain - -
+ + +
+
+
+
- +
diff --git a/examples/test-dashboard/simple-agent.html b/examples/test-dashboard/simple-agent.html index 01f33e152..cff04b2c0 100644 --- a/examples/test-dashboard/simple-agent.html +++ b/examples/test-dashboard/simple-agent.html @@ -112,6 +112,7 @@

Read Records

diff --git a/karma.conf.cjs b/karma.conf.cjs index 210dd3ff3..be5a485fa 100644 --- a/karma.conf.cjs +++ b/karma.conf.cjs @@ -29,7 +29,7 @@ module.exports = function (config) { // list of files / patterns to load in the browser files: [ - { pattern: 'tests/**/*.spec.js', watched: false } + { pattern: 'tests/**/*.spec.js', watched: false }, ], // preprocess matching files before serving them to the browser diff --git a/package-lock.json b/package-lock.json index 470b3632e..7f3b59181 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,23 +1,24 @@ { "name": "@tbd54566975/web5", - "version": "0.5.0", + "version": "0.6.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@tbd54566975/web5", - "version": "0.5.0", + "version": "0.6.0", "license": "Apache-2.0", "dependencies": { - "@decentralized-identity/ion-tools": "1.0.6", - "@tbd54566975/dwn-sdk-js": "0.0.26", + "@decentralized-identity/ion-tools": "1.0.7", + "@tbd54566975/dwn-sdk-js": "0.0.30", "cross-fetch": "3.1.5", "ed2curve": "0.3.0", + "readable-web-to-node-stream": "3.0.2", "tweetnacl": "1.0.3" }, "devDependencies": { "chai": "4.3.7", - "chai-as-promised": "^7.1.1", + "chai-as-promised": "7.1.1", "esbuild": "0.16.17", "eslint": "8.36.0", "karma": "6.4.1", @@ -50,6 +51,16 @@ "node": ">=0.1.90" } }, + "node_modules/@decentralized-identity/ion-pow-sdk": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@decentralized-identity/ion-pow-sdk/-/ion-pow-sdk-1.0.17.tgz", + "integrity": "sha512-vk7DTDM8aKDbFyu1ad/qkoRrGL4q+KvNeL/FNZXhkWPaDhVExBN/qGEoRLf1YSfFe+myto3+4RYTPut+riiqnw==", + "dependencies": { + "buffer": "6.0.3", + "cross-fetch": "3.1.5", + "hash-wasm": "4.9.0" + } + }, "node_modules/@decentralized-identity/ion-sdk": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@decentralized-identity/ion-sdk/-/ion-sdk-0.6.0.tgz", @@ -91,15 +102,15 @@ "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" }, "node_modules/@decentralized-identity/ion-tools": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@decentralized-identity/ion-tools/-/ion-tools-1.0.6.tgz", - "integrity": "sha512-xi1QudddgxUBzkN0cyerIZxxrZvBCO1RB3xf/eEbh0ZeSZe/WMgAEPjS3ehfUwAsDMKnf2REC3CPk5SphBA9OA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@decentralized-identity/ion-tools/-/ion-tools-1.0.7.tgz", + "integrity": "sha512-Rhz1pCBG3teq9PdeG54JC5KSwhgwkvY4FWufpsRpabrb2i5nPr804i4KfEwXzIdQxxJmzUVeS3+ypkrccl4WVQ==", "dependencies": { + "@decentralized-identity/ion-pow-sdk": "1.0.17", "@decentralized-identity/ion-sdk": "0.6.0", "@noble/ed25519": "1.6.0", "@noble/secp256k1": "1.5.5", "cross-fetch": "3.1.5", - "ion-pow-sdk": "1.0.16", "multiformats": "9.6.4" }, "engines": { @@ -852,6 +863,17 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "node_modules/@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, "node_modules/@sinonjs/commons": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", @@ -917,23 +939,27 @@ "integrity": "sha512-aWItSZvJj4+GI6FWkjZR13xPNPctq2RRakzo+O6vN7bC2yjwdg5EFpgaSAUn95b7BGSgcflvzVDPoKmJv24IOg==" }, "node_modules/@tbd54566975/dwn-sdk-js": { - "version": "0.0.26", - "resolved": "https://registry.npmjs.org/@tbd54566975/dwn-sdk-js/-/dwn-sdk-js-0.0.26.tgz", - "integrity": "sha512-Bj+cesS5ePQ1a8k+x5DFDQFwL8gNidOicW+/+VC/KjMH2QLikCuWYdcW9A3toxPJqtTQLlfXDBp5vLeO94C7/Q==", + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@tbd54566975/dwn-sdk-js/-/dwn-sdk-js-0.0.30.tgz", + "integrity": "sha512-PzO2r61G0dT3b32sBoR93ykBpkmLTDOc0vQAnqpiCRbX+Dx/3+yORbOFYLe4oKFYf/EnsohaiSXs9utcPpk6fQ==", "dependencies": { "@ipld/dag-cbor": "9.0.0", "@js-temporal/polyfill": "0.4.3", "@noble/ed25519": "1.7.1", "@noble/secp256k1": "1.7.1", + "@scure/base": "1.1.1", "@swc/helpers": "0.3.8", + "@types/eccrypto": "1.1.3", "@types/ms": "0.7.31", "@types/node": "^18.13.0", "@types/readable-stream": "2.3.15", + "@types/secp256k1": "4.0.3", "abstract-level": "1.0.3", "ajv": "8.11.0", "blockstore-core": "3.0.0", "cross-fetch": "3.1.5", "date-fns": "2.28.0", + "eccrypto": "1.1.6", "flat": "^5.0.2", "interface-blockstore": "4.0.1", "ipfs-unixfs": "6.0.9", @@ -946,6 +972,8 @@ "multiformats": "11.0.2", "randombytes": "2.1.0", "readable-stream": "4.3.0", + "secp256k1": "5.0.0", + "ulid": "2.3.0", "uuid": "8.3.2", "varint": "6.0.0" }, @@ -1012,6 +1040,15 @@ "@types/node": "*" } }, + "node_modules/@types/eccrypto": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@types/eccrypto/-/eccrypto-1.1.3.tgz", + "integrity": "sha512-3O0qER6JMYReqVbcQTGmXeMHdw3O+rVps63tlo5g5zoB3altJS8yzSvboSivwVWeYO9o5jSATu7P0UIqYZPgow==", + "dependencies": { + "@types/expect": "^1.20.4", + "@types/node": "*" + } + }, "node_modules/@types/eslint": { "version": "8.21.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.1.tgz", @@ -1041,6 +1078,11 @@ "dev": true, "peer": true }, + "node_modules/@types/expect": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", + "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==" + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -1072,6 +1114,14 @@ "safe-buffer": "~5.1.1" } }, + "node_modules/@types/secp256k1": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz", + "integrity": "sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -1483,6 +1533,24 @@ "node": ">=8" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bip66": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz", + "integrity": "sha512-nemMHz95EmS38a26XbbdxIYj5csHd3RMP3H5bwQknX0WYHF01qhpufP42mLOwVICuH2JmhIhXiWs89MfUGL7Xw==", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/bl": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", @@ -1602,8 +1670,7 @@ "node_modules/brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "dev": true + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" }, "node_modules/browser-level": { "version": "1.0.1", @@ -1635,7 +1702,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, + "devOptional": true, "dependencies": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -1801,7 +1868,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", - "dev": true + "devOptional": true }, "node_modules/builtin-status-codes": { "version": "3.0.0", @@ -1999,7 +2066,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, + "devOptional": true, "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -2159,7 +2226,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, + "devOptional": true, "dependencies": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -2172,7 +2239,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, + "devOptional": true, "dependencies": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", @@ -2432,6 +2499,72 @@ "url": "https://bevry.me/fund" } }, + "node_modules/drbg.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz", + "integrity": "sha512-F4wZ06PvqxYLFEZKkFxTDcns9oFNk34hvmJSEwdzsxVQ8YI5YaxtACgQatkYgv2VI2CFkUd2Y+xosPQnHv809g==", + "optional": true, + "dependencies": { + "browserify-aes": "^1.0.6", + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/eccrypto": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/eccrypto/-/eccrypto-1.1.6.tgz", + "integrity": "sha512-d78ivVEzu7Tn0ZphUUaL43+jVPKTMPFGtmgtz1D0LrFn7cY3K8CdrvibuLz2AAkHBLKZtR8DMbB2ukRYFk987A==", + "hasInstallScript": true, + "dependencies": { + "acorn": "7.1.1", + "elliptic": "6.5.4", + "es6-promise": "4.2.8", + "nan": "2.14.0" + }, + "optionalDependencies": { + "secp256k1": "3.7.1" + } + }, + "node_modules/eccrypto/node_modules/acorn": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/eccrypto/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "optional": true + }, + "node_modules/eccrypto/node_modules/secp256k1": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.7.1.tgz", + "integrity": "sha512-1cf8sbnRreXrQFdH6qsg2H71Xw91fCCS9Yp021GnUNJzWJS/py96fS4lHbnTnouLp08Xj6jBoBB6V78Tdbdu5g==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "bindings": "^1.5.0", + "bip66": "^1.1.5", + "bn.js": "^4.11.8", + "create-hash": "^1.2.0", + "drbg.js": "^1.0.1", + "elliptic": "^6.4.1", + "nan": "^2.14.0", + "safe-buffer": "^5.1.2" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/ed2curve": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/ed2curve/-/ed2curve-0.3.0.tgz", @@ -2457,7 +2590,6 @@ "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -2471,8 +2603,7 @@ "node_modules/elliptic/node_modules/bn.js": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -2580,6 +2711,11 @@ "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==", "dev": true }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, "node_modules/esbuild": { "version": "0.16.17", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz", @@ -3007,7 +3143,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, + "devOptional": true, "dependencies": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" @@ -3057,6 +3193,12 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -3413,7 +3555,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, + "devOptional": true, "dependencies": { "inherits": "^2.0.4", "readable-stream": "^3.6.0", @@ -3427,7 +3569,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, + "devOptional": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -3441,7 +3583,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -3458,15 +3600,14 @@ ] }, "node_modules/hash-wasm": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/hash-wasm/-/hash-wasm-4.4.1.tgz", - "integrity": "sha512-6hbcJY74kQQOAoDQZgm0rku51jlf0f3+g+q+1CI3vNvabF27SQuVJm86FnMNMaw8WEjBeW4U5GWX1KsKd8qZCw==" + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/hash-wasm/-/hash-wasm-4.9.0.tgz", + "integrity": "sha512-7SW7ejyfnRxuOc7ptQHSf4LDoZaWOivfzqw+5rpcQku0nHfmicPKE51ra9BiRLAmT8+gGLestr1XroUkqdjL6w==" }, "node_modules/hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" @@ -3485,7 +3626,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dev": true, "dependencies": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -3636,32 +3776,6 @@ "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-2.0.2.tgz", "integrity": "sha512-rScRlhDcz6k199EkHqT8NpM87ebN89ICOzILoBHgaG36/WX50N32BnU/kpZgCGPLhARRAWUUX5/cyaIjt7Kipg==" }, - "node_modules/ion-pow-sdk": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/ion-pow-sdk/-/ion-pow-sdk-1.0.16.tgz", - "integrity": "sha512-J5rwzf3Ntyv+mEUs+Mpor78E83hYsq8CDSf3SkxJXAajb4NYa3+medR7SoolpkMjEaMd2KeZg/+2w2XdrIrtwA==", - "dependencies": { - "buffer": "6.0.3", - "cross-fetch": "3.1.2", - "hash-wasm": "4.4.1" - } - }, - "node_modules/ion-pow-sdk/node_modules/cross-fetch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.2.tgz", - "integrity": "sha512-+JhD65rDNqLbGmB3Gzs3HrEKC0aQnD+XA3SY6RjgkF88jV2q5cTc5+CwxlS3sdmLk98gpPt5CF9XRnPdlxZe6w==", - "dependencies": { - "node-fetch": "2.6.1" - } - }, - "node_modules/ion-pow-sdk/node_modules/node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", - "engines": { - "node": "4.x || >=6.0.0" - } - }, "node_modules/ipfs-unixfs": { "version": "6.0.9", "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-6.0.9.tgz", @@ -4583,7 +4697,7 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, + "devOptional": true, "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1", @@ -4661,14 +4775,12 @@ "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "node_modules/minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", - "dev": true + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" }, "node_modules/minimatch": { "version": "3.1.2", @@ -5002,6 +5114,11 @@ "node": ">=8.0.0" } }, + "node_modules/nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + }, "node_modules/nanoid": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", @@ -5063,6 +5180,11 @@ "type-detect": "4.0.8" } }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, "node_modules/node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -5778,6 +5900,34 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", + "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "dependencies": { + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -5919,7 +6069,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, + "devOptional": true, "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1" @@ -6034,6 +6184,20 @@ "dev": true, "peer": true }, + "node_modules/secp256k1": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-5.0.0.tgz", + "integrity": "sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA==", + "hasInstallScript": true, + "dependencies": { + "elliptic": "^6.5.4", + "node-addon-api": "^5.0.0", + "node-gyp-build": "^4.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -6059,7 +6223,7 @@ "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, + "devOptional": true, "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -6772,6 +6936,14 @@ "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" }, + "node_modules/ulid": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.3.0.tgz", + "integrity": "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==", + "bin": { + "ulid": "bin/cli.js" + } + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -7229,6 +7401,16 @@ "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true }, + "@decentralized-identity/ion-pow-sdk": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@decentralized-identity/ion-pow-sdk/-/ion-pow-sdk-1.0.17.tgz", + "integrity": "sha512-vk7DTDM8aKDbFyu1ad/qkoRrGL4q+KvNeL/FNZXhkWPaDhVExBN/qGEoRLf1YSfFe+myto3+4RYTPut+riiqnw==", + "requires": { + "buffer": "6.0.3", + "cross-fetch": "3.1.5", + "hash-wasm": "4.9.0" + } + }, "@decentralized-identity/ion-sdk": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@decentralized-identity/ion-sdk/-/ion-sdk-0.6.0.tgz", @@ -7260,15 +7442,15 @@ } }, "@decentralized-identity/ion-tools": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@decentralized-identity/ion-tools/-/ion-tools-1.0.6.tgz", - "integrity": "sha512-xi1QudddgxUBzkN0cyerIZxxrZvBCO1RB3xf/eEbh0ZeSZe/WMgAEPjS3ehfUwAsDMKnf2REC3CPk5SphBA9OA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@decentralized-identity/ion-tools/-/ion-tools-1.0.7.tgz", + "integrity": "sha512-Rhz1pCBG3teq9PdeG54JC5KSwhgwkvY4FWufpsRpabrb2i5nPr804i4KfEwXzIdQxxJmzUVeS3+ypkrccl4WVQ==", "requires": { + "@decentralized-identity/ion-pow-sdk": "1.0.17", "@decentralized-identity/ion-sdk": "0.6.0", "@noble/ed25519": "1.6.0", "@noble/secp256k1": "1.5.5", "cross-fetch": "3.1.5", - "ion-pow-sdk": "1.0.16", "multiformats": "9.6.4" }, "dependencies": { @@ -7745,6 +7927,11 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==" + }, "@sinonjs/commons": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", @@ -7814,23 +8001,27 @@ "integrity": "sha512-aWItSZvJj4+GI6FWkjZR13xPNPctq2RRakzo+O6vN7bC2yjwdg5EFpgaSAUn95b7BGSgcflvzVDPoKmJv24IOg==" }, "@tbd54566975/dwn-sdk-js": { - "version": "0.0.26", - "resolved": "https://registry.npmjs.org/@tbd54566975/dwn-sdk-js/-/dwn-sdk-js-0.0.26.tgz", - "integrity": "sha512-Bj+cesS5ePQ1a8k+x5DFDQFwL8gNidOicW+/+VC/KjMH2QLikCuWYdcW9A3toxPJqtTQLlfXDBp5vLeO94C7/Q==", + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@tbd54566975/dwn-sdk-js/-/dwn-sdk-js-0.0.30.tgz", + "integrity": "sha512-PzO2r61G0dT3b32sBoR93ykBpkmLTDOc0vQAnqpiCRbX+Dx/3+yORbOFYLe4oKFYf/EnsohaiSXs9utcPpk6fQ==", "requires": { "@ipld/dag-cbor": "9.0.0", "@js-temporal/polyfill": "0.4.3", "@noble/ed25519": "1.7.1", "@noble/secp256k1": "1.7.1", + "@scure/base": "1.1.1", "@swc/helpers": "0.3.8", + "@types/eccrypto": "1.1.3", "@types/ms": "0.7.31", "@types/node": "^18.13.0", "@types/readable-stream": "2.3.15", + "@types/secp256k1": "4.0.3", "abstract-level": "1.0.3", "ajv": "8.11.0", "blockstore-core": "3.0.0", "cross-fetch": "3.1.5", "date-fns": "2.28.0", + "eccrypto": "1.1.6", "flat": "^5.0.2", "interface-blockstore": "4.0.1", "ipfs-unixfs": "6.0.9", @@ -7843,6 +8034,8 @@ "multiformats": "11.0.2", "randombytes": "2.1.0", "readable-stream": "4.3.0", + "secp256k1": "5.0.0", + "ulid": "2.3.0", "uuid": "8.3.2", "varint": "6.0.0" }, @@ -7888,6 +8081,15 @@ "@types/node": "*" } }, + "@types/eccrypto": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@types/eccrypto/-/eccrypto-1.1.3.tgz", + "integrity": "sha512-3O0qER6JMYReqVbcQTGmXeMHdw3O+rVps63tlo5g5zoB3altJS8yzSvboSivwVWeYO9o5jSATu7P0UIqYZPgow==", + "requires": { + "@types/expect": "^1.20.4", + "@types/node": "*" + } + }, "@types/eslint": { "version": "8.21.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.1.tgz", @@ -7917,6 +8119,11 @@ "dev": true, "peer": true }, + "@types/expect": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", + "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==" + }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -7948,6 +8155,14 @@ "safe-buffer": "~5.1.1" } }, + "@types/secp256k1": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz", + "integrity": "sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==", + "requires": { + "@types/node": "*" + } + }, "@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -8297,6 +8512,24 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bip66": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz", + "integrity": "sha512-nemMHz95EmS38a26XbbdxIYj5csHd3RMP3H5bwQknX0WYHF01qhpufP42mLOwVICuH2JmhIhXiWs89MfUGL7Xw==", + "optional": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, "bl": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", @@ -8398,8 +8631,7 @@ "brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "dev": true + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" }, "browser-level": { "version": "1.0.1", @@ -8431,7 +8663,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, + "devOptional": true, "requires": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -8552,7 +8784,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", - "dev": true + "devOptional": true }, "builtin-status-codes": { "version": "3.0.0", @@ -8684,7 +8916,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, + "devOptional": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -8826,7 +9058,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, + "devOptional": true, "requires": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -8839,7 +9071,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, + "devOptional": true, "requires": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", @@ -9049,6 +9281,58 @@ "integrity": "sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw==", "dev": true }, + "drbg.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz", + "integrity": "sha512-F4wZ06PvqxYLFEZKkFxTDcns9oFNk34hvmJSEwdzsxVQ8YI5YaxtACgQatkYgv2VI2CFkUd2Y+xosPQnHv809g==", + "optional": true, + "requires": { + "browserify-aes": "^1.0.6", + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4" + } + }, + "eccrypto": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/eccrypto/-/eccrypto-1.1.6.tgz", + "integrity": "sha512-d78ivVEzu7Tn0ZphUUaL43+jVPKTMPFGtmgtz1D0LrFn7cY3K8CdrvibuLz2AAkHBLKZtR8DMbB2ukRYFk987A==", + "requires": { + "acorn": "7.1.1", + "elliptic": "6.5.4", + "es6-promise": "4.2.8", + "nan": "2.14.0", + "secp256k1": "3.7.1" + }, + "dependencies": { + "acorn": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==" + }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "optional": true + }, + "secp256k1": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.7.1.tgz", + "integrity": "sha512-1cf8sbnRreXrQFdH6qsg2H71Xw91fCCS9Yp021GnUNJzWJS/py96fS4lHbnTnouLp08Xj6jBoBB6V78Tdbdu5g==", + "optional": true, + "requires": { + "bindings": "^1.5.0", + "bip66": "^1.1.5", + "bn.js": "^4.11.8", + "create-hash": "^1.2.0", + "drbg.js": "^1.0.1", + "elliptic": "^6.4.1", + "nan": "^2.14.0", + "safe-buffer": "^5.1.2" + } + } + } + }, "ed2curve": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/ed2curve/-/ed2curve-0.3.0.tgz", @@ -9074,7 +9358,6 @@ "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, "requires": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -9088,8 +9371,7 @@ "bn.js": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" } } }, @@ -9181,6 +9463,11 @@ "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==", "dev": true }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, "esbuild": { "version": "0.16.17", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz", @@ -9502,7 +9789,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, + "devOptional": true, "requires": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" @@ -9549,6 +9836,12 @@ "flat-cache": "^3.0.4" } }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -9806,7 +10099,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, + "devOptional": true, "requires": { "inherits": "^2.0.4", "readable-stream": "^3.6.0", @@ -9817,7 +10110,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, + "devOptional": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -9828,20 +10121,19 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "devOptional": true } } }, "hash-wasm": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/hash-wasm/-/hash-wasm-4.4.1.tgz", - "integrity": "sha512-6hbcJY74kQQOAoDQZgm0rku51jlf0f3+g+q+1CI3vNvabF27SQuVJm86FnMNMaw8WEjBeW4U5GWX1KsKd8qZCw==" + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/hash-wasm/-/hash-wasm-4.9.0.tgz", + "integrity": "sha512-7SW7ejyfnRxuOc7ptQHSf4LDoZaWOivfzqw+5rpcQku0nHfmicPKE51ra9BiRLAmT8+gGLestr1XroUkqdjL6w==" }, "hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, "requires": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" @@ -9857,7 +10149,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dev": true, "requires": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -9974,31 +10265,6 @@ "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-2.0.2.tgz", "integrity": "sha512-rScRlhDcz6k199EkHqT8NpM87ebN89ICOzILoBHgaG36/WX50N32BnU/kpZgCGPLhARRAWUUX5/cyaIjt7Kipg==" }, - "ion-pow-sdk": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/ion-pow-sdk/-/ion-pow-sdk-1.0.16.tgz", - "integrity": "sha512-J5rwzf3Ntyv+mEUs+Mpor78E83hYsq8CDSf3SkxJXAajb4NYa3+medR7SoolpkMjEaMd2KeZg/+2w2XdrIrtwA==", - "requires": { - "buffer": "6.0.3", - "cross-fetch": "3.1.2", - "hash-wasm": "4.4.1" - }, - "dependencies": { - "cross-fetch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.2.tgz", - "integrity": "sha512-+JhD65rDNqLbGmB3Gzs3HrEKC0aQnD+XA3SY6RjgkF88jV2q5cTc5+CwxlS3sdmLk98gpPt5CF9XRnPdlxZe6w==", - "requires": { - "node-fetch": "2.6.1" - } - }, - "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" - } - } - }, "ipfs-unixfs": { "version": "6.0.9", "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-6.0.9.tgz", @@ -10669,7 +10935,7 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, + "devOptional": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1", @@ -10731,14 +10997,12 @@ "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", - "dev": true + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" }, "minimatch": { "version": "3.1.2", @@ -10983,6 +11247,11 @@ "resolved": "https://registry.npmjs.org/murmurhash3js-revisited/-/murmurhash3js-revisited-3.0.0.tgz", "integrity": "sha512-/sF3ee6zvScXMb1XFJ8gDsSnY+X8PbOyjIuBhtgis10W2Jx4ZjIhikUCIF9c4gpJxVnQIsPAFrSwTCuAjicP6g==" }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + }, "nanoid": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", @@ -11037,6 +11306,11 @@ } } }, + "node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, "node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -11563,6 +11837,26 @@ "process": "^0.11.10" } }, + "readable-web-to-node-stream": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", + "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "requires": { + "readable-stream": "^3.6.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -11663,7 +11957,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, + "devOptional": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1" @@ -11739,6 +12033,16 @@ } } }, + "secp256k1": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-5.0.0.tgz", + "integrity": "sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA==", + "requires": { + "elliptic": "^6.5.4", + "node-addon-api": "^5.0.0", + "node-gyp-build": "^4.2.0" + } + }, "serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -11764,7 +12068,7 @@ "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, + "devOptional": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -12292,6 +12596,11 @@ } } }, + "ulid": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.3.0.tgz", + "integrity": "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==" + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", diff --git a/package.json b/package.json index 37f5caa50..8a230fa1c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tbd54566975/web5", - "version": "0.5.0", + "version": "0.6.0", "description": "SDK for accessing the features and capabilities of Web5", "type": "module", "main": "./dist/cjs/index.cjs", @@ -49,7 +49,7 @@ "license": "Apache-2.0", "devDependencies": { "chai": "4.3.7", - "chai-as-promised": "^7.1.1", + "chai-as-promised": "7.1.1", "esbuild": "0.16.17", "eslint": "8.36.0", "karma": "6.4.1", @@ -68,10 +68,11 @@ "source-map-loader": "4.0.1" }, "dependencies": { - "@decentralized-identity/ion-tools": "1.0.6", - "@tbd54566975/dwn-sdk-js": "0.0.26", + "@decentralized-identity/ion-tools": "1.0.7", + "@tbd54566975/dwn-sdk-js": "0.0.30", "cross-fetch": "3.1.5", "ed2curve": "0.3.0", + "readable-web-to-node-stream": "3.0.2", "tweetnacl": "1.0.3" } } diff --git a/src/did/connect/connect.js b/src/did/connect/connect.js index 3d7305e68..14a5694fe 100644 --- a/src/did/connect/connect.js +++ b/src/did/connect/connect.js @@ -1,9 +1,9 @@ -import { DIDConnectRPCMethods, DIDConnectStep, JSONRPCErrorCodes, findWebSocketListener } from './utils.js'; +import { DidConnectRpcMethods, DidConnectStep, JsonRpcErrorCodes, findWebSocketListener } from './utils.js'; import { WebSocketClient } from './ws-client.js'; -import { parseJSON } from '../../utils.js'; -import { LocalStorage } from '../../storage/LocalStorage.js'; +import { parseJson } from '../../utils.js'; +import { LocalStorage } from '../../storage/local-storage.js'; -export class DIDConnect { +export class DidConnect { #web5; #client = null; @@ -11,7 +11,6 @@ export class DIDConnect { #permissionsRequests = []; - // TEMP // TODO: Replace this once the DID Manager and Keystore have been implemented #storage = null; #didStoreName = null; @@ -77,17 +76,17 @@ export class DIDConnect { async #initiateWeb5Client() { // Handler that will be used to step through the DIDConnect process phases const handleMessage = async (event) => { - const rpcMessage = parseJSON(event.data); + const rpcMessage = parseJson(event.data); switch (connectStep) { - case DIDConnectStep.Initiation: { + case DidConnectStep.Initiation: { // The Client App initiates the DIDConnect process, so no messages from the Provider are expected until the Verification step console.warn('Unexpected message received before Web5 Client was ready'); break; } - case DIDConnectStep.Verification: { + case DidConnectStep.Verification: { const verificationResult = rpcMessage?.result; // Encrypted PIN challenge received from DIDConnect Provider if (verificationResult?.ok) { @@ -95,18 +94,18 @@ export class DIDConnect { const pinBytes = await this.web5.did.decrypt({ did: this.#did.id, payload: verificationResult.payload, - privateKey: this.#did.keys[0].keypair.privateKeyJwk.d, // TODO: Remove once a keystore has been implemented + privateKey: this.#did.keys[0].keyPair.privateKeyJwk.d, // TODO: Remove once a keystore has been implemented }); - const pin = this.web5.dwn.SDK.Encoder.bytesToString(pinBytes); + const pin = this.web5.dwn.sdk.Encoder.bytesToString(pinBytes); // Emit event notifying the DWA that the PIN can be displayed to the end user this.web5.dispatchEvent(new CustomEvent('challenge', { detail: { pin } })); // Advance DIDConnect to Delegation and wait for challenge response from DIDConect Provider - connectStep = DIDConnectStep.Delegation; + connectStep = DidConnectStep.Delegation; // Send queued PermissionsRequest to Provider. - this.#client.sendRequest(DIDConnectRPCMethods[connectStep], { message: this.#permissionsRequests.pop() }); + this.#client.sendRequest(DidConnectRpcMethods[connectStep], { message: this.#permissionsRequests.pop() }); } else { // TODO: Remove socket listeners, destroy socket, destroy this.#client, and emit error to notify user of app @@ -114,16 +113,15 @@ export class DIDConnect { break; } - case DIDConnectStep.Delegation: { + case DidConnectStep.Delegation: { const delegationResult = rpcMessage?.result; // Success if (delegationResult?.ok) { const authorizedDid = delegationResult?.message?.grantedBy; - // Register DID now that the connection was authorized - await this.web5.did.register({ + // Set Managed DID now that the connection was authorized + await this.web5.did.manager.set(authorizedDid, { connected: true, - did: authorizedDid, endpoint: `http://localhost:${this.#client.port}/dwn`, }); @@ -157,10 +155,10 @@ export class DIDConnect { this.#client = null; const { code = undefined, message = undefined } = delegationError; - if (code === JSONRPCErrorCodes.Unauthorized) { + if (code === JsonRpcErrorCodes.Unauthorized) { // Emit event notifying the DWA that the connection request was denied this.web5.dispatchEvent(new CustomEvent('denied', { detail: { message: message } })); - } else if (code === JSONRPCErrorCodes.Forbidden) { + } else if (code === JsonRpcErrorCodes.Forbidden) { // Emit event notifying the DWA that this app has been blocked from connecting this.web5.dispatchEvent(new CustomEvent('blocked', { detail: { message: message } })); } @@ -168,27 +166,27 @@ export class DIDConnect { // Reached terminal DID Connect state where connection was either authorized/denied/block // Reset DID Connect step to be ready for any future reconnects/switch account/change permission requests - connectStep = DIDConnectStep.Initiation; + connectStep = DidConnectStep.Initiation; break; } } }; - let connectStep = DIDConnectStep.Initiation; + let connectStep = DidConnectStep.Initiation; // Pre-Flight Check: Is the Web5 Client already connected to the Provider? If NO, try to connect. let connectedToProvider = this.#alreadyConnected() || await this.#connectWeb5Provider(); if (!connectedToProvider) return; - + // Start listening for messages from the DIDConnect Provider this.#client.addEventListener('message', handleMessage); // Send a request to the agent initiating the DIDConnect process - this.#client.sendRequest(DIDConnectRPCMethods.Initiation); + this.#client.sendRequest(DidConnectRpcMethods.Initiation); // Advance DIDConnect to Verification and wait for encrypted challenge PIN from DIDConnect Provider - connectStep = DIDConnectStep.Verification; + connectStep = DidConnectStep.Verification; } @@ -244,7 +242,7 @@ export class DIDConnect { if (this.#did === null) throw new Error('Unexpected state: DID data and configuration should have already been initialized'); // Dynamically generate DID Connect path in case origin has changed. - const encodedOrigin = this.#web5.dwn.SDK.Encoder.stringToBase64Url(location.origin); + const encodedOrigin = this.#web5.dwn.sdk.Encoder.stringToBase64Url(location.origin); const connectPath = `didconnect/${this.#did.id}/${encodedOrigin}`; let socket, startPort, endPort, userInitiatedAction, host; diff --git a/src/did/connect/utils.js b/src/did/connect/utils.js index 507a4c250..3567acf2f 100644 --- a/src/did/connect/utils.js +++ b/src/did/connect/utils.js @@ -1,12 +1,12 @@ -import { parseJSON, triggerProtocolHandler } from '../../utils.js'; +import { parseJson } from '../../utils.js'; -export const DIDConnectRPCMethods = { +export const DidConnectRpcMethods = { Ready: 'didconnect.ready', Initiation: 'didconnect.initiation', Delegation: 'didconnect.delegation', }; -export const JSONRPCErrorCodes = { +export const JsonRpcErrorCodes = { // JSON-RPC 2.0 pre-defined errors InvalidRequest: -32600, MethodNotFound: -32601, @@ -20,7 +20,7 @@ export const JSONRPCErrorCodes = { Forbidden: -50403, // equivalent to HTTP Status 403 }; -export const DIDConnectStep = { +export const DidConnectStep = { Initiation: 'Initiation', Verification: 'Verification', Delegation: 'Delegation', @@ -62,8 +62,8 @@ export async function findWebSocketListener( clearListeners(socket); // Resolve and complete connection only if the expected message is received from the agent. - const message = parseJSON(event.data); - if (message?.method === DIDConnectRPCMethods.Ready) { + const message = parseJson(event.data); + if (message?.method === DidConnectRpcMethods.Ready) { // Resolve and return the open socket back to the caller resolve(socket); } else { @@ -103,4 +103,12 @@ export async function findWebSocketListener( } } } -} \ No newline at end of file +} + +export async function triggerProtocolHandler(url) { + let form = document.createElement('form'); + form.action = url; + document.body.append(form); + form.submit(); + form.remove(); +} diff --git a/src/did/connect/ws-client.js b/src/did/connect/ws-client.js index 391437e9f..49f49a329 100644 --- a/src/did/connect/ws-client.js +++ b/src/did/connect/ws-client.js @@ -1,5 +1,3 @@ -import { parseJSON } from '../../utils.js'; - export class WebSocketClient { #port; #requestID = 0; diff --git a/src/did/crypto/x25519-xsalsa20-poly1305.js b/src/did/crypto/x25519-xsalsa20-poly1305.js index c618a9165..aea0d7aba 100644 --- a/src/did/crypto/x25519-xsalsa20-poly1305.js +++ b/src/did/crypto/x25519-xsalsa20-poly1305.js @@ -1,7 +1,7 @@ import nacl from 'tweetnacl'; import { Encoder } from '@tbd54566975/dwn-sdk-js'; -import { ed25519PrivateKeyToX25519, ed25519PublicKeyToX25519, verificationMethodToPublicKeyBytes } from '../../did/didUtils.js'; +import { ed25519PrivateKeyToX25519, ed25519PublicKeyToX25519, verificationMethodToPublicKeyBytes } from '../../did/utils.js'; import { bytesToObject, objectValuesBase64UrlToBytes, objectValuesBytesToBase64Url } from '../../utils.js'; export class X25519Xsalsa20Poly1305 { @@ -69,7 +69,7 @@ export class X25519Xsalsa20Poly1305 { // Convert recipient's Ed25519 public key to X25519 const recipientDHPublicKey = ed25519PublicKeyToX25519(recipientPublicKey); - // Generate ephemeral keypair + // Generate ephemeral keyPair const ephemeralKeyPair = nacl.box.keyPair(); // Generate new nonce for every operation diff --git a/src/did/manager.js b/src/did/manager.js new file mode 100644 index 000000000..71aa3167a --- /dev/null +++ b/src/did/manager.js @@ -0,0 +1,28 @@ +export class DidManager { + #store; + + constructor(options) { + this.#store = options.store; + } + + async clear() { + this.#store.clear(); + } + + async exists(id) { + const value = await this.#store.get(id); + return value !== undefined; + } + + async get(id) { + return this.#store.get(id); + } + + async delete(id) { + this.#store.delete(id); + } + + async set(id, value) { + this.#store.set(id, value); + } +} diff --git a/src/did/methods/ion.js b/src/did/methods/ion.js index f69653d04..4fa83c803 100644 --- a/src/did/methods/ion.js +++ b/src/did/methods/ion.js @@ -1,14 +1,16 @@ -import { DID, generateKeyPair, sign, verify } from '@decentralized-identity/ion-tools'; +import { DID, generateKeyPair } from '@decentralized-identity/ion-tools'; import { DidIonResolver } from '@tbd54566975/dwn-sdk-js'; +export { sign, verify } from '@decentralized-identity/ion-tools'; + const didIonResolver = new DidIonResolver(); -async function create(options = { }){ +export async function create(options = { }){ options.keys ||= [ { - id: 'key-1', + id: 'dwn', type: 'JsonWebKey2020', - keypair: await generateKeyPair(), + keyPair: await generateKeyPair(), purposes: ['authentication'], }, ]; @@ -17,8 +19,8 @@ async function create(options = { }){ content: { publicKeys: options.keys.map(key => { let pubkey = Object.assign({ }, key); - pubkey.publicKeyJwk = key.keypair.publicJwk; - delete pubkey.keypair; + pubkey.publicKeyJwk = key.keyPair.publicJwk; + delete pubkey.keyPair; return pubkey; }), ...(options.services && { services: options.services }), @@ -34,7 +36,7 @@ async function create(options = { }){ }; } -async function resolve(did) { +export async function resolve(did) { try { return await didIonResolver.resolve(did); } catch (error) { @@ -47,10 +49,3 @@ async function resolve(did) { }; } } - -export { - create, - sign, - verify, - resolve, -}; diff --git a/src/did/methods/key.js b/src/did/methods/key.js index ac645c69a..906c6cde9 100644 --- a/src/did/methods/key.js +++ b/src/did/methods/key.js @@ -7,11 +7,11 @@ import { MULTICODEC_ED25519_PUB_HEADER, MULTICODEC_X25519_PUB_HEADER, createJWK, -} from '../didUtils.js'; +} from '../utils.js'; const didKeyResolver = new DidKeyResolver(); -async function create(options = { }) { +export async function create(options = { }) { // Generate new sign key pair. const verificationKeyPair = nacl.sign.keyPair(); const keyAgreementKeyPair = ed25519KeyPairToX25519(verificationKeyPair); @@ -46,7 +46,7 @@ async function create(options = { }) { }; } -async function resolve(did) { +export async function resolve(did) { return didKeyResolver.resolve(did); } @@ -62,7 +62,7 @@ async function resolve(did) { * @param {string} options.privateKeyJwk.x Base64url encoded public key * @returns {Uint8Array} Signature */ -async function sign(options) { +export async function sign(options) { const { data, privateKeyJwk } = options; const privateKeyBytes = Encoder.base64UrlToBytes(privateKeyJwk.d); @@ -91,7 +91,7 @@ async function sign(options) { * @param {string} options.publicKeyJwk.x Base64url encoded public key * @returns {boolean} */ -async function verify(options) { +export async function verify(options) { const { signature, data, publicKeyJwk } = options; const publicKeyBytes = Encoder.base64UrlToBytes(publicKeyJwk.x); @@ -109,10 +109,3 @@ async function verify(options) { return (result === null) ? false : true; } - -export { - create, - resolve, - sign, - verify, -}; \ No newline at end of file diff --git a/src/did/didUtils.js b/src/did/utils.js similarity index 97% rename from src/did/didUtils.js rename to src/did/utils.js index 859ca0250..0542cf320 100644 --- a/src/did/didUtils.js +++ b/src/did/utils.js @@ -33,7 +33,7 @@ export const DID_VERIFICATION_RELATIONSHIPS = [ * @param {string} options.crv Cryptographic curve * @param {Uint8Array} options.publicKey Public key bytes * @param {Uint8Array} options.privateKey Private key bytes - * @returns {{ id: string, type: string, controller: string, keypair: Object}} + * @returns {{ id: string, type: string, controller: string, keyPair: Object}} */ export async function createJWK(options) { const { id, crv, kty, kid, publicKey, privateKey } = options; @@ -41,16 +41,16 @@ export async function createJWK(options) { id: `${id}#${kid}`, type: 'JsonWebKey2020', controller: id, - keypair: {}, + keyPair: {}, }; const jwk = { crv, kid, kty }; - jsonWebKey.keypair.publicKeyJwk = { ...jwk }; - jsonWebKey.keypair.publicKeyJwk.x = Encoder.bytesToBase64Url(publicKey); + jsonWebKey.keyPair.publicKeyJwk = { ...jwk }; + jsonWebKey.keyPair.publicKeyJwk.x = Encoder.bytesToBase64Url(publicKey); - jsonWebKey.keypair.privateKeyJwk = { ...jsonWebKey.keypair.publicKeyJwk }; - jsonWebKey.keypair.privateKeyJwk.d = Encoder.bytesToBase64Url(privateKey); + jsonWebKey.keyPair.privateKeyJwk = { ...jsonWebKey.keyPair.publicKeyJwk }; + jsonWebKey.keyPair.privateKeyJwk.d = Encoder.bytesToBase64Url(privateKey); return jsonWebKey; } diff --git a/src/did/Web5DID.js b/src/did/web5-did.js similarity index 80% rename from src/did/Web5DID.js rename to src/did/web5-did.js index c02723690..ab01f044f 100644 --- a/src/did/Web5DID.js +++ b/src/did/web5-did.js @@ -1,19 +1,20 @@ import { Encoder } from '@tbd54566975/dwn-sdk-js'; -import { DIDConnect } from './connect/connect.js'; +import { DidConnect } from './connect/connect.js'; import * as CryptoCiphers from './crypto/ciphers.js'; +import { DidManager } from './manager.js'; import * as Methods from './methods/methods.js'; -import * as DidUtils from './didUtils.js'; -import { MemoryStorage } from '../storage/MemoryStorage.js'; +import * as DidUtils from './utils.js'; +import { MemoryStorage } from '../storage/memory-storage.js'; import { pascalToKebabCase } from '../utils.js'; -class Web5DID { +export class Web5Did { #cryptoCiphers = {}; #didConnect; #web5; - #registeredDIDs = new MemoryStorage(); - #resolvedDIDs = new MemoryStorage(); + #didManager; + #resolvedDids = new MemoryStorage(); constructor(web5) { this.#web5 = web5; @@ -23,10 +24,12 @@ class Web5DID { this.#cryptoCiphers[cipherName] = new CryptoCiphers[cipher](this.web5); } - this.#didConnect = new DIDConnect(web5); - // Bind functions to the instance of DIDConnect + this.#didConnect = new DidConnect(web5); + // Bind functions to the instance of DidConnect this.#didConnect.connect = this.#didConnect.connect.bind(this.#didConnect); this.#didConnect.permissionsRequest = this.#didConnect.permissionsRequest.bind(this.#didConnect); + + this.#didManager = new DidManager({ store: new MemoryStorage() }); } get connect() { @@ -37,8 +40,18 @@ class Web5DID { return this.#didConnect.permissionsRequest; } + get manager() { + return { + clear: () => this.#didManager.clear(), + exists: (...args) => this.#didManager.exists(...args), + get: (...args) => this.#didManager.get(...args), + delete: (...args) => this.#didManager.delete(...args), + set: (...args) => this.#didManager.set(...args), + }; + } + get util() { - return this.#util; + return DidUtils; } get web5() { @@ -92,36 +105,23 @@ class Web5DID { return api.encrypt(options); } - async register(data) { - await this.#registeredDIDs.set(data.did, { - connected: data.connected, - did: data.did, // TODO: Consider removing if createAndSignMessage() no longer requires for Key ID - endpoint: data.endpoint, - keys: data.keys, - }); - } - async sign(method, options = { }) { const api = await this.#getMethodAPI(method); return api.sign(options); } - async unregister(did) { - await this.#registeredDIDs.delete(did); - } - async verify(method, options = { }) { const api = await this.#getMethodAPI(method); return api.verify(options); } async resolve(did, options = { }) { - const registered = await this.#registeredDIDs.get(did); - if (registered) { - return registered; + const managed = await this.manager.get(did); + if (managed) { + return managed; } - const resolved = await this.#resolvedDIDs.get(did); + const resolved = await this.#resolvedDids.get(did); if (resolved) { return resolved; } @@ -130,8 +130,8 @@ class Web5DID { const result = await api.resolve(did); if (options.cache) { - // store separately in case the DID is `register` after `resolve` was called - await this.#resolvedDIDs.set(did, result, { + // store separately in case the DID is `managed` after `resolve` was called. + await this.#resolvedDids.set(did, result, { timeout: 1000 * 60 * 60, // 1hr }); } @@ -174,13 +174,4 @@ class Web5DID { if (!api) throw `Unsupported cryptographic cipher: ${name}`; return api; } - - /** - * Utility functions for working with DIDs - */ - #util = { ...DidUtils }; } - -export { - Web5DID, -}; diff --git a/src/dwn/dwn-utils.js b/src/dwn/dwn-utils.js new file mode 100644 index 000000000..5e7a4462c --- /dev/null +++ b/src/dwn/dwn-utils.js @@ -0,0 +1,7 @@ +/** + * Uses duck typing to determine whether the stream is a web browser ReadableStream + * or a Node.js Readable stream. + */ +export function isReadableWebStream(stream) { + return typeof stream._read !== 'function'; +} diff --git a/src/dwn/interface/Records.js b/src/dwn/interface/Records.js deleted file mode 100644 index b2461896b..000000000 --- a/src/dwn/interface/Records.js +++ /dev/null @@ -1,37 +0,0 @@ -import { Interface } from './Interface.js'; -import { dataToBytes } from '../../utils.js'; - -class Records extends Interface { - constructor(dwn) { - super(dwn, 'Records'); - } - - async delete(target, request) { - return this.send('Delete', target, request); - } - - async read(target, request) { - return this.send('Read', target, request); - } - - async query(target, request) { - return this.send('Query', target, request); - } - - async write(target, request) { - // Convert string/object data to bytes before further processing. - const { dataBytes, dataFormat } = dataToBytes(request.data, request.message.dataFormat); - return this.send('Write', target, { - ...request, - data: dataBytes, - message: { - ...request.message, - dataFormat, - }, - }); - } -} - -export { - Records, -}; diff --git a/src/dwn/interface/Interface.js b/src/dwn/interfaces/interface.js similarity index 87% rename from src/dwn/interface/Interface.js rename to src/dwn/interfaces/interface.js index 1f3b009eb..ac57e99ca 100644 --- a/src/dwn/interface/Interface.js +++ b/src/dwn/interfaces/interface.js @@ -1,4 +1,4 @@ -class Interface { +export class Interface { #dwn; #name; @@ -7,6 +7,10 @@ class Interface { this.#name = name; } + get dwn() { + return this.#dwn; + } + // TODO: Remove this once Permissions implemented in dwn-sdk-js get permissionsRequest() { return this.#dwn.web5.did.permissionsRequest; @@ -23,7 +27,3 @@ class Interface { }); } } - -export { - Interface, -}; diff --git a/src/dwn/interface/Permissions.js b/src/dwn/interfaces/permissions.js similarity index 76% rename from src/dwn/interface/Permissions.js rename to src/dwn/interfaces/permissions.js index d5f53e470..65d3b215e 100644 --- a/src/dwn/interface/Permissions.js +++ b/src/dwn/interfaces/permissions.js @@ -1,7 +1,7 @@ import { v4 as uuid } from 'uuid'; -import { Interface } from './Interface.js'; +import { Interface } from './interface.js'; -class Permissions extends Interface { +export class Permissions extends Interface { constructor(dwn) { super(dwn, 'Permissions'); } @@ -18,7 +18,3 @@ class Permissions extends Interface { }); } } - -export { - Permissions, -}; diff --git a/src/dwn/interface/Protocols.js b/src/dwn/interfaces/protocols.js similarity index 69% rename from src/dwn/interface/Protocols.js rename to src/dwn/interfaces/protocols.js index a22e2d298..8e5a5930b 100644 --- a/src/dwn/interface/Protocols.js +++ b/src/dwn/interfaces/protocols.js @@ -1,6 +1,6 @@ -import { Interface } from './Interface.js'; +import { Interface } from './interface.js'; -class Protocols extends Interface { +export class Protocols extends Interface { constructor(dwn) { super(dwn, 'Protocols'); } @@ -13,7 +13,3 @@ class Protocols extends Interface { return this.send('Query', target, request); } } - -export { - Protocols, -}; diff --git a/src/dwn/interfaces/records.js b/src/dwn/interfaces/records.js new file mode 100644 index 000000000..95c70ea2f --- /dev/null +++ b/src/dwn/interfaces/records.js @@ -0,0 +1,104 @@ +import { DwnConstant } from '@tbd54566975/dwn-sdk-js'; + +import { Interface } from './interface.js'; +import { Record } from '../models/record.js'; +import { dataToBytes, isEmptyObject } from '../../utils.js'; + +export class Records extends Interface { + constructor(dwn) { + super(dwn, 'Records'); + } + + async create(...args) { + return this.write(...args); + } + + async createFrom(target, request) { + const { author: inheritedAuthor, ...inheritedProperties } = request.record.toJSON(); + + // Remove target from inherited properties since target is being explicitly defined in method parameters. + delete inheritedProperties.target; + + + // If `data` is being updated then `dataCid` and `dataSize` must not be present. + if (request.data !== undefined) { + delete inheritedProperties.dataCid; + delete inheritedProperties.dataSize; + } + + // If `published` is set to false, ensure that `datePublished` is undefined. Otherwise, DWN SDK's schema validation + // will throw an error if `published` is false but `datePublished` is set. + if (request.message?.published === false && inheritedProperties.datePublished !== undefined) { + delete inheritedProperties.datePublished; + delete inheritedProperties.published; + } + + // If the request changes the `author` or message `descriptor` then the deterministic `recordId` will change. + // As a result, we will discard the `recordId` if either of these changes occur. + if (!isEmptyObject(request.message) || (request.author && request.author !== inheritedAuthor)) { + delete inheritedProperties.recordId; + } + + return this.write(target, { + author: request.author || inheritedAuthor, + data: request.data, + message: { + ...inheritedProperties, + ...request.message, + }, + }); + } + + async delete(target, request) { + const response = await this.send('Delete', target, request); + return response; + } + + async read(target, request) { + const response = await this.send('Read', target, request); + + let record; + if (response?.record) { + record = new Record(this.dwn, { ...response.record, target, author: request.author }); + } + + return { ...response, record }; + } + + async query(target, request) { + const response = await this.send('Query', target, request); + const entries = response.entries.map(entry => new Record(this.dwn, { ...entry, target, author: request.author })); + + return { ...response, entries }; + } + + async write(target, request) { + let dataBytes, dataFormat; + if (request?.data) { + // If `data` is specified, convert string/object data to bytes before further processing. + ({ dataBytes, dataFormat } = dataToBytes(request.data, request.message.dataFormat)); + } else { + // If not, `dataFormat` must be specified in the request message. + dataFormat = request.message.dataFormat; + } + + const response = await this.send('Write', target, { + ...request, + data: dataBytes, + message: { + ...request.message, + dataFormat, + }, + }); + + let record; + if (response?.message) { + // Include data if `dataSize` is less than DWN 'max data size allowed to be encoded'. + const encodedData = (response.message?.descriptor?.dataSize <= DwnConstant.maxDataSizeAllowedToBeEncoded) ? dataBytes : null; + + record = new Record(this.dwn, { ...response.message, encodedData, target, author: request.author }); + } + + return { ...response, record }; + } +} diff --git a/src/dwn/models/record.js b/src/dwn/models/record.js new file mode 100644 index 000000000..b75ebb4c1 --- /dev/null +++ b/src/dwn/models/record.js @@ -0,0 +1,238 @@ +import { DataStream, DwnConstant, Encoder } from '@tbd54566975/dwn-sdk-js'; +import { ReadableWebToNodeStream } from 'readable-web-to-node-stream'; + +import { isReadableWebStream } from '../dwn-utils.js'; + +export class Record { + #dwn; + + #author; + #contextId; + #descriptor; + #recordId; + #target; + + #encodedData = null; + #isDeleted = false; + #readableStream = null; + + constructor(dwn, options = { }) { + this.#dwn = dwn; + + // RecordsWriteMessage properties. + const { author, contextId = undefined, descriptor, recordId = null, target } = options; + this.#contextId = contextId; + this.#descriptor = descriptor ?? { }; + delete this.#descriptor.data; + this.#recordId = recordId; + + // Store the target and author DIDs that were used to create the message to use for subsequent reads, etc. + this.#author = author; + this.#target = target; + + // If the record `dataSize is less than the DwnConstant.maxDataSizeAllowedToBeEncoded value, + // then an `encodedData` property will be present. + this.#encodedData = options?.encodedData ?? null; + + // If the record was created from a RecordsRead reply then it will have a `data` property. + if (options?.data) { + this.#readableStream = isReadableWebStream(options.data) ? new ReadableWebToNodeStream(options.data) : options.data; + } + } + + // Mutable Web5 Record Class properties. + get author() { return this.#author; } + get isDeleted() { return this.#isDeleted; } + get target() { return this.#target; } + set author(author) { this.#author = author; } + set target(target) { this.#target = target; } + + // Immutable DWN Record properties. + get id() { return this.#recordId; } + get contextId() { return this.#contextId; } + get dataFormat() { return this.#descriptor?.dataFormat; } + get dateCreated() { return this.#descriptor?.dateCreated; } + get interface() { return this.#descriptor?.interface; } + get method() { return this.#descriptor?.method; } + get parentId() { return this.#descriptor?.parentId; } + get protocol() { return this.#descriptor?.protocol; } + get recipient() { return this.#descriptor?.recipient; } + get schema() { return this.#descriptor?.schema; } + + // Mutable DWN Record properties. + get dataCid() { return this.#descriptor?.dataCid; } + get dataSize() { return this.#descriptor?.dataSize; } + get dateModified() { return this.#descriptor?.dateModified; } + get datePublished() { return this.#descriptor?.datePublished; } + get published() { return this.#descriptor?.published; } + + /** + * Data handling. + */ + + get data() { + if (!this.#encodedData && !this.#readableStream) { + // `encodedData` will be set if `dataSize` <= DwnConstant.maxDataSizeAllowedToBeEncoded. (10KB as of April 2023) + // `readableStream` will be set if Record was instantiated from a RecordsRead reply. + // If neither of the above are true, then the record must be fetched from the DWN. + this.#readableStream = this.#dwn.records.read(this.#target, { author: this.#author, message: { recordId: this.#recordId } }) + .then((response) => response.record ) + .then((record) => record.data); + } + + if (typeof this.#encodedData === 'string') { + // If `encodedData` is set, then it is expected that: + // `dataSize` <= DwnConstant.maxDataSizeAllowedToBeEncoded (10KB as of April 2023) + // type is Uint8Array bytes if the Record object was instantiated from a RecordsWrite response + // type is Base64 URL encoded string if the Record object was instantiated from a RecordsQuery response + // If it is a string, we need to Base64 URL decode to bytes + this.#encodedData = Encoder.base64UrlToBytes(this.#encodedData); + } + + const self = this; // Capture the context of the `Record` instance. + const dataObj = { + async json() { + if (self.#encodedData) return this.text().then(JSON.parse); + if (self.#readableStream) return this.text().then(JSON.parse); + return null; + }, + async text() { + if (self.#encodedData) return Encoder.bytesToString(self.#encodedData); + if (self.#readableStream) return self.#readableStream.then(DataStream.toBytes).then(Encoder.bytesToString); + return null; + }, + async stream() { + if (self.#encodedData) return DataStream.fromBytes(self.#encodedData); + if (self.#readableStream) return self.#readableStream; + return null; + }, + then(...callbacks) { + return this.stream().then(...callbacks); + }, + catch(callback) { + return dataObj.then().catch(callback); + }, + }; + return dataObj; + } + + /** + * Record mutation methods. + */ + + async delete() { + if (this.isDeleted) throw new Error(`Error: Record with ID '${this.id}' was previously deleted.`); + + // Attempt to delete the record from the DWN. + const response = await this.#dwn.records.delete(this.#target, { author: this.#author, message: { recordId: this.#recordId } }); + + if (response.status.code === 202) { + // If the record was successfully deleted, mark the instance as deleted to prevent further modifications. + this.#setDeletedStatus(true); + } + + return response; + } + + async update(options = { }) { + if (this.isDeleted) throw new Error(`Error: Record with ID '${this.id}' was previously deleted.`); + + // Begin assembling update message. + let updateMessage = { ...this.#descriptor, ...options }; + + // If `data` is being updated then `dataCid` and `dataSize` must be undefined and the `data` property is passed as + // a top-level property to `web5.dwn.records.write()`. + let data; + if (options.data !== undefined) { + delete updateMessage.dataCid; + delete updateMessage.dataSize; + data = options.data; + delete options.data; + } + + // Throw an error if an attempt is made to modify immutable properties. `data` has already been handled. + const mutableDescriptorProperties = ['dataCid', 'dataSize', 'dateModified', 'datePublished', 'published']; + Record.#verifyPermittedMutation(Object.keys(options), mutableDescriptorProperties); + + // If a new `dateModified` was not provided, remove it from the updateMessage to let the DWN SDK auto-fill. + // This is necessary because otherwise DWN SDK throws an Error 409 Conflict due to attempting to overwrite a record + // when the `dateModified` timestamps are identical. + if (options.dateModified === undefined) { + delete updateMessage.dateModified; + } + + // If `published` is set to false, ensure that `datePublished` is undefined. Otherwise, DWN SDK's schema validation + // will throw an error if `published` is false but `datePublished` is set. + if (options.published === false && updateMessage.datePublished !== undefined) { + delete updateMessage.datePublished; + } + + // Set the record ID and context ID, if any. + updateMessage.recordId = this.#recordId; + updateMessage.contextId = this.#contextId; + + // Attempt to write the changes to mutable properties to the DWN. + const { message = null, record = null, status } = await this.#dwn.records.write(this.#target, { + author: this.#author, + data, + message: { + ...updateMessage, + }, + }); + + if (status.code === 202 && record) { + // Only update the local Record instance mutable properties if the record was successfully (over)written. + mutableDescriptorProperties.forEach(property => { + this.#descriptor[property] = record[property]; + }); + // Only update data if `dataSize` is less than DWN 'max data size allowed to be encoded'. + if (data !== undefined) { + this.#readableStream = (record.dataSize <= DwnConstant.maxDataSizeAllowedToBeEncoded) ? record.data : null; + this.#encodedData = null; // Clear `encodedData` in case it was previously set. + } + } + + return { message, status }; + } + + /** + * Utility methods. + */ + + /** + * Called by `JSON.stringify(...)` automatically. + */ + toJSON() { + return { + author: this.author, + target: this.target, + recordId: this.id, + contextId: this.contextId, + dataFormat: this.dataFormat, + dateCreated: this.dateCreated, + interface: this.interface, + method: this.method, + parentId: this.parentId, + protocol: this.protocol, + recipient: this.recipient, + schema: this.schema, + dataCid: this.dataCid, + dataSize: this.dataSize, + dateModified: this.dateModified, + datePublished: this.datePublished, + published: this.published, + }; + } + + #setDeletedStatus(status) { + this.#isDeleted = status; + } + + static #verifyPermittedMutation(propertiesToMutate, mutableDescriptorProperties) { + for (const property of propertiesToMutate) { + if (!mutableDescriptorProperties.includes(property)) { + throw new Error(`${property} is an immutable property. Its value cannot be changed.`); + } + } + } +} diff --git a/src/dwn/Web5DWN.js b/src/dwn/web5-dwn.js similarity index 66% rename from src/dwn/Web5DWN.js rename to src/dwn/web5-dwn.js index fe45a2713..606653766 100644 --- a/src/dwn/Web5DWN.js +++ b/src/dwn/web5-dwn.js @@ -1,13 +1,13 @@ -import * as SDK from '@tbd54566975/dwn-sdk-js'; +import * as Sdk from '@tbd54566975/dwn-sdk-js'; -import { Permissions } from './interface/Permissions.js'; -import { Protocols } from './interface/Protocols.js'; -import { Records } from './interface/Records.js'; +import { Permissions } from './interfaces/permissions.js'; +import { Protocols } from './interfaces/protocols.js'; +import { Records } from './interfaces/records.js'; import { createWeakSingletonAccessor } from '../utils.js'; -const sharedNode = createWeakSingletonAccessor(() => SDK.Dwn.create()); +const sharedNode = createWeakSingletonAccessor(() => Sdk.Dwn.create()); -class Web5DWN { +export class Web5Dwn { #web5; #node; @@ -27,8 +27,8 @@ class Web5DWN { this.#records = new Records(this); } - get SDK() { - return SDK; + get sdk() { + return Sdk; } get web5() { @@ -51,7 +51,3 @@ class Web5DWN { return this.#node ??= sharedNode(); } } - -export { - Web5DWN, -}; diff --git a/src/main.js b/src/main.js index 803e648dc..4aaef5787 100644 --- a/src/main.js +++ b/src/main.js @@ -1 +1 @@ -export { Web5 } from './Web5.js'; +export { Web5 } from './web5.js'; diff --git a/src/storage/LocalStorage.js b/src/storage/local-storage.js similarity index 71% rename from src/storage/LocalStorage.js rename to src/storage/local-storage.js index e1ecbd208..84ef90262 100644 --- a/src/storage/LocalStorage.js +++ b/src/storage/local-storage.js @@ -1,6 +1,6 @@ -import { Storage } from './Storage.js'; +import { Storage } from './storage.js'; -class LocalStorage extends Storage { +export class LocalStorage extends Storage { async get(key) { return JSON.parse(localStorage.getItem(key)); } @@ -17,7 +17,3 @@ class LocalStorage extends Storage { localStorage.clear(); } } - -export { - LocalStorage, -}; diff --git a/src/storage/MemoryStorage.js b/src/storage/memory-storage.js similarity index 86% rename from src/storage/MemoryStorage.js rename to src/storage/memory-storage.js index a1d5f23fc..3b15808f2 100644 --- a/src/storage/MemoryStorage.js +++ b/src/storage/memory-storage.js @@ -1,6 +1,6 @@ -import { Storage } from './Storage.js'; +import { Storage } from './storage.js'; -class MemoryStorage extends Storage { +export class MemoryStorage extends Storage { #dataForKey = new Map; #timeoutForKey = new Map; @@ -35,7 +35,3 @@ class MemoryStorage extends Storage { this.#timeoutForKey.clear(); } } - -export { - MemoryStorage, -}; diff --git a/src/storage/Storage.js b/src/storage/storage.js similarity index 86% rename from src/storage/Storage.js rename to src/storage/storage.js index fcc91fee2..0862faed8 100644 --- a/src/storage/Storage.js +++ b/src/storage/storage.js @@ -1,4 +1,4 @@ -class Storage { +export class Storage { async get(_key) { throw 'subclass must override'; } @@ -15,7 +15,3 @@ class Storage { throw 'subclass must override'; } } - -export { - Storage, -}; diff --git a/src/transport/AppTransport.js b/src/transport/app-transport.js similarity index 80% rename from src/transport/AppTransport.js rename to src/transport/app-transport.js index 2ea3d25dd..b6ea5e07e 100644 --- a/src/transport/AppTransport.js +++ b/src/transport/app-transport.js @@ -1,8 +1,8 @@ import { DataStream } from '@tbd54566975/dwn-sdk-js'; -import { Transport } from './Transport.js'; +import { Transport } from './transport.js'; -class AppTransport extends Transport { +export class AppTransport extends Transport { async encodeData(data) { return DataStream.fromBytes(data); } @@ -17,7 +17,3 @@ class AppTransport extends Transport { return node.processMessage(request.target, request.message.message, encodedData); } } - -export { - AppTransport, -}; diff --git a/src/transport/HTTPTransport.js b/src/transport/http-transport.js similarity index 59% rename from src/transport/HTTPTransport.js rename to src/transport/http-transport.js index e4d202382..7c3110b8d 100644 --- a/src/transport/HTTPTransport.js +++ b/src/transport/http-transport.js @@ -1,7 +1,7 @@ import crossFetch from 'cross-fetch'; import { Encoder } from '@tbd54566975/dwn-sdk-js'; -import { Transport } from './Transport.js'; +import { Transport } from './transport.js'; /** * Supports fetch in: browser, browser extensions, Node, and React Native. @@ -16,8 +16,9 @@ import { Transport } from './Transport.js'; */ const fetch = globalThis.fetch ?? crossFetch; -class HTTPTransport extends Transport { +export class HttpTransport extends Transport { ENCODED_MESSAGE_HEADER = 'DWN-MESSAGE'; + ENCODED_RESPONSE_HEADER = 'WEB5-RESPONSE'; async encodeMessage(message) { return Encoder.stringToBase64Url(JSON.stringify(message)); @@ -28,7 +29,7 @@ class HTTPTransport extends Transport { } async send(endpoint, request) { // override - return fetch(endpoint, { + const response = await fetch(endpoint, { method: 'POST', mode: 'cors', cache: 'no-cache', @@ -41,17 +42,21 @@ class HTTPTransport extends Transport { 'Content-Type': 'application/octet-stream', }, body: request.data, - }) - .then((response) => { - // Only resolve if response was successful (status of 200-299) - if (response.ok) { - return response; - } - return Promise.reject(response); - }); + }); + + if (!response.ok) { + throw new Error(`Fetch failed with status ${response.status}`); + } + + const web5ResponseHeader = response.headers.get(this.ENCODED_RESPONSE_HEADER); + if (web5ResponseHeader) { + // RecordsRead responses return `message` and `status` as header values, with a `data` ReadableStream in the body. + const { entries = null, message, record, status } = await this.decodeMessage(web5ResponseHeader); + return { entries, message, record: { ...record, data: response.body }, status }; + + } else { + // All other DWN responses return `entries`, `message`, and `status` as stringified JSON in the body. + return await response.json(); + } } } - -export { - HTTPTransport, -}; diff --git a/src/transport/Transport.js b/src/transport/transport.js similarity index 80% rename from src/transport/Transport.js rename to src/transport/transport.js index 348a86c06..c6ac61151 100644 --- a/src/transport/Transport.js +++ b/src/transport/transport.js @@ -1,4 +1,4 @@ -class Transport { +export class Transport { #web5; constructor(web5) { @@ -13,7 +13,3 @@ class Transport { throw 'subclass must override'; } } - -export { - Transport, -}; diff --git a/src/types.js b/src/types.js new file mode 100644 index 000000000..879bc159b --- /dev/null +++ b/src/types.js @@ -0,0 +1,217 @@ +/** + * NOTE: These are temporary until Web5 JS can be converted to TypeScript and import the types from dwn-sdk-js. + */ + +/** + * DWN SDK JS type definitions converted from TypeScript. + */ + +/** + * @typedef {Object} BaseMessage + * @property {Descriptor} descriptor + * @property {GeneralJws} authorization + */ + +/** + * @typedef {Object} Descriptor + * @property {string} interface + * @property {string} method + * @property {string} [dataCid] + * @property {number} [dataSize] + */ + +/** + * @typedef {Object} GeneralJws + * @property {string} payload + * @property {SignatureEntry[]} signatures + */ + +/** + * TODO: Readable isn't resolved. Expected solution is to import types from dwn-sdk-js once Web5 JS is converted to TS. + * @typedef {Object} MessageReplyOptions + * @property {Status} status + * @property {QueryResultEntry[]} [entries] + * @property {Readable} [data] + */ + +/** + * @typedef {Object} QueryResultEntry + * @property {Descriptor} descriptor + * @property {string} [encodedData] + */ + +/** + * @typedef {Object} SignatureEntry + * @property {string} protected + * @property {string} signature + */ + +/** + * @typedef {Object} Status + * @property {number} code + * @property {string} detail + */ + +/** + * @typedef {Object} ProtocolsConfigureDescriptor + * @property {DwnInterfaceName.Protocols} interface + * @property {DwnMethodName.Configure} method + * @property {string} dateCreated + * @property {string} protocol + * @property {ProtocolDefinition} definition + */ + +/** + * @typedef {Object} ProtocolDefinition + * @property {Object} labels + * @property {Object} records + */ + +/** + * @typedef {Object} ProtocolRuleSet + * @property {Object} [allow] + * @property {Object} [allow.anyone] + * @property {string[]} [allow.anyone.to] + * @property {Object} [allow.recipient] + * @property {string} [allow.recipient.of] + * @property {string[]} [allow.recipient.to] + * @property {Object} [records] + */ + +/** + * @typedef {BaseMessage & Object} ProtocolsConfigureMessage + * @property {ProtocolsConfigureDescriptor} descriptor + */ + +/** + * @typedef {Object} ProtocolsQueryDescriptor + * @property {DwnInterfaceName.Protocols} interface + * @property {DwnMethodName.Query} method + * @property {string} dateCreated + * @property {Object} [filter] + * @property {string} [filter.protocol] + */ + +/** + * @typedef {BaseMessage & Object} ProtocolsQueryMessage + * @property {ProtocolsQueryDescriptor} descriptor + */ + +/** + * @typedef {Object} RecordsWriteDescriptor + * @property {DwnInterfaceName.Records} interface + * @property {DwnMethodName.Write} method + * @property {string} [protocol] + * @property {string} recipient + * @property {string} [schema] + * @property {string} [parentId] + * @property {string} dataCid + * @property {number} dataSize + * @property {string} dateCreated + * @property {string} dateModified + * @property {boolean} [published] + * @property {string} [datePublished] + * @property {string} dataFormat + */ + +/** + * @typedef {BaseMessage & Object} RecordsWriteMessage + * @property {string} recordId + * @property {string} [contextId] + * @property {RecordsWriteDescriptor} descriptor + * @property {GeneralJws} [attestation] + */ + +/** + * @typedef {Object} RecordsQueryDescriptor + * @property {DwnInterfaceName.Records} interface + * @property {DwnMethodName.Query} method + * @property {string} dateCreated + * @property {RecordsQueryFilter} filter + * @property {DateSort} [dateSort] + */ + +/** + * @typedef {'Records'} DwnInterfaceName.Records + */ +/** + * @typedef {'Hooks'} DwnInterfaceName.Hooks + */ +/** + * @typedef {'Protocols'} DwnInterfaceName.Protocols + */ +/** + * @typedef {'Permissions'} DwnInterfaceName.Permissions + */ +/** + * @typedef {'Configure'} DwnMethodName.Configure + */ +/** + * @typedef {'Grant'} DwnMethodName.Grant + */ +/** + * @typedef {'Query'} DwnMethodName.Query + */ +/** + * @typedef {'Read'} DwnMethodName.Read + */ +/** + * @typedef {'Request'} DwnMethodName.Request + */ +/** + * @typedef {'Write'} DwnMethodName.Write + */ +/** + * @typedef {'Delete'} DwnMethodName.Delete + */ + +/** + * @typedef {Object} RecordsQueryFilter + * @property {string} [attester] + * @property {string} [recipient] + * @property {string} [protocol] + * @property {string} [contextId] + * @property {string} [schema] + * @property {string} [recordId] + * @property {string} [parentId] + * @property {string} [dataFormat] + * @property {RangeCriterion} [dateCreated] + */ + +/** + * @typedef {Object} RangeCriterion + * @property {string} [from] + * @property {string} [to] + */ + +/** + * @typedef {BaseMessage & Object} RecordsQueryMessage + * @property {RecordsQueryDescriptor} descriptor + */ + +/** + * @typedef {Object} RecordsReadMessage + * @property {GeneralJws} [authorization] + * @property {RecordsReadDescriptor} descriptor + */ + +/** + * @typedef {Object} RecordsReadDescriptor + * @property {DwnInterfaceName.Records} interface + * @property {DwnMethodName.Read} method + * @property {string} recordId + * @property {string} date + */ + +/** + * @typedef {BaseMessage & Object} RecordsDeleteMessage + * @property {RecordsDeleteDescriptor} descriptor + */ + +/** + * @typedef {Object} RecordsDeleteDescriptor + * @property {DwnInterfaceName.Records} interface + * @property {DwnMethodName.Delete} method + * @property {string} recordId + * @property {string} dateModified + */ diff --git a/src/utils.js b/src/utils.js index 468e989a3..c15314258 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,4 +1,3 @@ -import nacl from 'tweetnacl'; import { Encoder } from '@tbd54566975/dwn-sdk-js'; const textDecoder = new TextDecoder(); @@ -15,7 +14,7 @@ export function bytesToObject(bytes) { return JSON.parse(objectString); } -function createWeakSingletonAccessor(creator) { +export function createWeakSingletonAccessor(creator) { let weakref = null; return function() { let object = weakref?.deref(); @@ -27,14 +26,14 @@ function createWeakSingletonAccessor(creator) { }; } -function isEmptyObject(obj) { +export function isEmptyObject(obj) { if (typeof obj === 'object' && obj !== null) { return Object.keys(obj).length === 0; } return false; } -function parseJSON(str) { +export function parseJson(str) { try { return JSON.parse(str); } catch { @@ -42,7 +41,7 @@ function parseJSON(str) { } } -function parseURL(str) { +export function parseUrl(str) { try { return new URL(str); } catch { @@ -50,7 +49,7 @@ function parseURL(str) { } } -function pascalToKebabCase(str) { +export function pascalToKebabCase(str) { return str .replace(/([a-z0-9])([A-Z])/g, '$1-$2') .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2') @@ -60,7 +59,7 @@ function pascalToKebabCase(str) { /** * Set/detect the media type and return the data as bytes. */ -const dataToBytes = (data, dataFormat) => { +export const dataToBytes = (data, dataFormat) => { let dataBytes = data; // Check for Object or String, and if neither, assume bytes. @@ -89,15 +88,15 @@ const dataToBytes = (data, dataFormat) => { * @param {{}} message * @returns boolean */ -function isUnsignedMessage(message) { +export function isUnsignedMessage(message) { return message?.message?.authorization ? false : true; } -function objectValuesBytesToBase64Url(obj) { +export function objectValuesBytesToBase64Url(obj) { return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, Encoder.bytesToBase64Url(value)])); } -function objectValuesBase64UrlToBytes(obj) { +export function objectValuesBase64UrlToBytes(obj) { return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, Encoder.base64UrlToBytes(value)])); } @@ -110,34 +109,3 @@ function objectValuesBase64UrlToBytes(obj) { const toType = (obj) => { return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase(); }; - -async function triggerProtocolHandler(url) { - let form = document.createElement('form'); - form.action = url; - document.body.append(form); - form.submit(); - form.remove(); -} - -async function decodePin(data, secretKey) { - const { pin, nonce, publicKey } = data; - const encryptedPinBytes = Encoder.base64UrlToBytes(pin); - const nonceBytes = new TextEncoder().encode(nonce); - const publicKeyBytes = Encoder.base64UrlToBytes(publicKey); - const encodedPin = nacl.box.open(encryptedPinBytes, nonceBytes, publicKeyBytes, secretKey); - data.pin = new TextDecoder().decode(encodedPin); -} - -export { - createWeakSingletonAccessor, - dataToBytes, - decodePin, - isEmptyObject, - isUnsignedMessage, - objectValuesBase64UrlToBytes, - objectValuesBytesToBase64Url, - parseJSON, - parseURL, - pascalToKebabCase, - triggerProtocolHandler, -}; diff --git a/src/Web5.js b/src/web5.js similarity index 68% rename from src/Web5.js rename to src/web5.js index 1f6a6dd5b..767c6e468 100644 --- a/src/Web5.js +++ b/src/web5.js @@ -1,10 +1,10 @@ -import { Web5DID } from './did/Web5DID.js'; -import { Web5DWN } from './dwn/Web5DWN.js'; -import { AppTransport } from './transport/AppTransport.js'; -import { HTTPTransport } from './transport/HTTPTransport.js'; -import { isUnsignedMessage, parseURL } from './utils.js'; +import { Web5Did } from './did/web5-did.js'; +import { Web5Dwn } from './dwn/web5-dwn.js'; +import { AppTransport } from './transport/app-transport.js'; +import { HttpTransport } from './transport/http-transport.js'; +import { isUnsignedMessage, parseUrl } from './utils.js'; -class Web5 extends EventTarget { +export class Web5 extends EventTarget { #dwn; #did; #transports; @@ -12,12 +12,12 @@ class Web5 extends EventTarget { constructor(options = { }) { super(); - this.#dwn = new Web5DWN(this, options?.dwn); - this.#did = new Web5DID(this); + this.#dwn = new Web5Dwn(this, options?.dwn); + this.#did = new Web5Did(this); this.#transports = { app: new AppTransport(this), - http: new HTTPTransport(this), - https: new HTTPTransport(this), + http: new HttpTransport(this), + https: new HttpTransport(this), }; } @@ -54,10 +54,10 @@ class Web5 extends EventTarget { } // TODO: Is this sufficient or might we improve how the calling app can respond by initiating a connect/re-connect flow? - return { status: { code: 422, detail: 'Local keys not available and remote agent not connected' } }; + return { status: { code: 401, detail: 'Local keys not available and remote agent not connected' } }; } - message = await this.#createSignedMessage(resolvedAuthor, message, data); + message = await this.#createSignedMessage(author, resolvedAuthor, message, data); } const resolvedTarget = await this.#did.resolve(target); @@ -74,15 +74,16 @@ class Web5 extends EventTarget { return { status: { code: 422, detail: 'No DWN endpoints present in DID document. Request cannot be sent.' } }; } - return { status: { code: 422, detail: 'Target DID could not be resolved' } }; + return { status: { code: 400, detail: 'Target DID could not be resolved' } }; } - async #createSignedMessage(resolvedAuthor, message, data) { - const authorizationSignatureInput = this.#dwn.SDK.Jws.createSignatureInput({ - keyId: resolvedAuthor.did + '#key-1', - keyPair: resolvedAuthor.keys, + async #createSignedMessage(author, resolvedAuthor, message, data) { + const keyId = '#dwn'; + const authorizationSignatureInput = this.#dwn.sdk.Jws.createSignatureInput({ + keyId: author + keyId, + keyPair: resolvedAuthor.keys[keyId].keyPair, }); - const signedMessage = await this.#dwn.SDK[message.interface + message.method].create({ + const signedMessage = await this.#dwn.sdk[message.interface + message.method].create({ ...message, authorizationSignatureInput, data, @@ -91,6 +92,15 @@ class Web5 extends EventTarget { return signedMessage; } + /** + * @typedef {Object} Web5SendResponseMessage + * @property {ProtocolsConfigureDescriptor | ProtocolsQueryDescriptor | RecordsQueryDescriptor | RecordsReadDescriptor | RecordsWriteDescriptor} message + */ + + /** + * @typedef {MessageReplyOptions | Web5SendResponseMessage} Web5SendResponse + */ + /** * Sends the message to one or more endpoint URIs * @@ -108,25 +118,29 @@ class Web5 extends EventTarget { * @param {*} request.data - The message data (if any). * @param {object} request.message - The DWeb message. * @param {string} request.target - The DID to send the message to. - * @returns Promise + * @returns {Promise} */ async #send(endpoints, request) { let response; for (let endpoint of endpoints) { try { - const url = parseURL(endpoint); + const url = parseUrl(endpoint); response = await this.#transports[url?.protocol?.slice(0, -1)]?.send(url.href, request); } catch (error) { - console.log(error); + console.error(error); // Intentionally ignore exception and try the next endpoint. } if (response) break; // Stop looping and return after the first endpoint successfully responds. } - return response ?? { status: { code: 503, detail: 'Service Unavailable' } }; + if (response && !isUnsignedMessage(request.message)) { + // If the message is signed return the `descriptor`, and if present, `recordId`. + const { recordId = null, descriptor } = request.message.message; + response.message = { recordId, descriptor }; + } + + response ??= { status: { code: 503, detail: 'Service Unavailable' } }; + + return response; } } - -export { - Web5, -}; diff --git a/tests/data/didDocuments.js b/tests/data/did-documents.js similarity index 100% rename from tests/data/didDocuments.js rename to tests/data/did-documents.js diff --git a/tests/did/methods/ion.spec.js b/tests/did/methods/ion.spec.js index f860c6f9e..986167492 100644 --- a/tests/did/methods/ion.spec.js +++ b/tests/did/methods/ion.spec.js @@ -1,14 +1,14 @@ import { expect } from 'chai'; import sinon from 'sinon'; -import { Web5DID } from '../../../src/did/Web5DID.js'; -import * as didDocuments from '../../data/didDocuments.js'; +import { Web5Did } from '../../../src/did/web5-did.js'; +import * as didDocuments from '../../data/did-documents.js'; -describe('Web5DID', async () => { +describe('Web5Did', async () => { let web5did; beforeEach(function () { - web5did = new Web5DID(); + web5did = new Web5Did(); }); before(function () { @@ -19,6 +19,30 @@ describe('Web5DID', async () => { this.clock.restore(); }); + describe('create', async () => { + it('should return one key when creating a did:ion DID', async () => { + const did = await web5did.create('ion'); + expect(did.keys).to.have.lengthOf(1); + }); + + it('should return one purpose, authentication, when creating a did:ion DID', async () => { + const did = await web5did.create('ion'); + expect(did.keys[0].purposes).to.have.lengthOf(1); + expect(did.keys[0].purposes[0]).to.equal('authentication'); + }); + + it('should return keys with a default `dwn` keyId when creating a did:ion DID', async () => { + const did = await web5did.create('ion'); + expect(did.keys[0].id).to.equal('dwn'); + }); + + it('should return keys in JWK format when creating a did:ion DID', async () => { + const did = await web5did.create('ion'); + expect(did.keys[0].keyPair).to.have.property('privateJwk'); + expect(did.keys[0].keyPair).to.have.property('publicJwk'); + }); + }); + describe('getDidDocument', async () => { it('should return a didDocument for a valid did:ion DID', async () => { sinon.stub(web5did, 'resolve').resolves(didDocuments.ion.oneVerificationMethodJwk); @@ -58,17 +82,16 @@ describe('Web5DID', async () => { }); describe('resolve', async () => { - it('should not call ion-tools resolve() when registered DID is cached', async () => { - // If registered DID isn't cached, the fetch() call to resolve over the network + it('should not call ion-tools resolve() when managed DID is cached', async () => { + // If managed DID isn't cached, the fetch() call to resolve over the network // will take far more than 10ms timeout, causing the test to fail. const did = 'did:ion:EiClkZMDxPKqC9c-umQfTkR8vvZ9JPhl_xLDI9Nfk38w5w'; const didData = { connected: true, - did: did, endpoint: 'http://localhost:55500', }; - web5did.register(didData); + web5did.manager.set(did, didData); const _ = await web5did.resolve(did); }).timeout(10); diff --git a/tests/did/methods/key.spec.js b/tests/did/methods/key.spec.js index f8d0f9250..2132c828a 100644 --- a/tests/did/methods/key.spec.js +++ b/tests/did/methods/key.spec.js @@ -1,14 +1,47 @@ import { expect } from 'chai'; import sinon from 'sinon'; -import { Web5DID } from '../../../src/did/Web5DID.js'; -import * as didDocuments from '../../data/didDocuments.js'; +import { Web5Did } from '../../../src/did/web5-did.js'; +import * as didDocuments from '../../data/did-documents.js'; -describe('Web5DID', async () => { +describe('Web5Did', async () => { let web5did; beforeEach(function () { - web5did = new Web5DID(); + web5did = new Web5Did(); + }); + + describe('create', async () => { + it('should return two keys when creating a did:key DID', async () => { + const did = await web5did.create('key'); + expect(did.keys).to.have.lengthOf(2); + }); + + it('should return two keys with keyId in form `did:key:id#publicKeyKeyId` when creating a did:key DID', async () => { + const did = await web5did.create('key'); + expect(did.keys[0].id).to.equal(`${did.id}#${did.keys[0].keyPair.publicKeyJwk.kid}`); + expect(did.keys[1].id).to.equal(`${did.id}#${did.keys[1].keyPair.publicKeyJwk.kid}`); + }); + + it('should return keys in JWK format when creating a did:key DID', async () => { + const did = await web5did.create('key'); + expect(did.keys[0].type).to.equal('JsonWebKey2020'); + expect(did.keys[1].type).to.equal('JsonWebKey2020'); + expect(did.keys[0].keyPair).to.have.property('privateKeyJwk'); + expect(did.keys[0].keyPair).to.have.property('publicKeyJwk'); + expect(did.keys[1].keyPair).to.have.property('privateKeyJwk'); + expect(did.keys[1].keyPair).to.have.property('publicKeyJwk'); + }); + + it('should return first key using Ed25519 curve when creating a did:key DID', async () => { + const did = await web5did.create('key'); + expect(did.keys[0].keyPair.publicKeyJwk.crv).to.equal('Ed25519'); + }); + + it('should return second key using X25519 curve when creating a did:key DID', async () => { + const did = await web5did.create('key'); + expect(did.keys[1].keyPair.publicKeyJwk.crv).to.equal('X25519'); + }); }); describe('getDidDocument', async () => { @@ -40,12 +73,12 @@ describe('Web5DID', async () => { expect(resolved.didDocument).to.have.property('id', did); }); - it('should return null didDocument for an invalid DID', async () => { + it('should return undefined didDocument for an invalid DID', async () => { const did = 'did:key:invalid'; const resolved = await web5did.resolve(did); - expect(resolved.didDocument).to.be.null; + expect(resolved.didDocument).to.be.undefined; expect(resolved.didResolutionMetadata.error).to.equal('invalidDid'); }); }); diff --git a/tests/did/didUtils.spec.js b/tests/did/utils.spec.js similarity index 98% rename from tests/did/didUtils.spec.js rename to tests/did/utils.spec.js index 5397e833c..2a71b5c8b 100644 --- a/tests/did/didUtils.spec.js +++ b/tests/did/utils.spec.js @@ -1,12 +1,12 @@ import chaiAsPromised from 'chai-as-promised'; import chai, { expect } from 'chai'; -import * as DidUtils from '../../src/did/didUtils.js'; -import * as didDocuments from '../data/didDocuments.js'; +import * as DidUtils from '../../src/did/utils.js'; +import * as didDocuments from '../data/did-documents.js'; chai.use(chaiAsPromised); -describe('Web5DID', async () => { +describe('Web5Did', async () => { describe('DID Utils Tests', () => { diff --git a/tests/did/Web5DID.spec.js b/tests/did/web5-did.spec.js similarity index 89% rename from tests/did/Web5DID.spec.js rename to tests/did/web5-did.spec.js index cd3cd54a9..bd9652459 100644 --- a/tests/did/Web5DID.spec.js +++ b/tests/did/web5-did.spec.js @@ -3,15 +3,15 @@ import sinon from 'sinon'; import { Encoder } from '@tbd54566975/dwn-sdk-js'; import { base64UrlToString } from '../../src/utils.js'; -import { Web5 } from '../../src/Web5.js'; -import { Web5DID } from '../../src/did/Web5DID.js'; -import * as didDocuments from '../data/didDocuments.js'; +import { Web5 } from '../../src/web5.js'; +import { Web5Did } from '../../src/did/web5-did.js'; +import * as didDocuments from '../data/did-documents.js'; -describe('Web5DID', async () => { +describe('Web5Did', async () => { let web5did; beforeEach(function () { - web5did = new Web5DID(); + web5did = new Web5Did(); }); before(function () { @@ -42,7 +42,7 @@ describe('Web5DID', async () => { const decryptionResult = await web5.did.decrypt({ did: recipientDid.id, - privateKey: recipientDid.keys[0].keypair.privateKeyJwk.d, + privateKey: recipientDid.keys[0].keyPair.privateKeyJwk.d, payload: encryptionResult, }); @@ -146,39 +146,40 @@ describe('Web5DID', async () => { }); }); - describe('register', async () => { - it('should never expire registered DIDs', async function () { + + describe('manager', async () => { + it('should never expire managed DIDs', async function () { let resolved; const did = 'did:ion:abcd1234'; const didData = { connected: true, - did: did, endpoint: 'http://localhost:55500', }; - web5did.register(didData); + await web5did.manager.set(did, didData); resolved = await web5did.resolve(did); - expect(resolved.did).to.equal(did); + expect(resolved).to.not.be.undefined; + expect(resolved).to.equal(didData); this.clock.tick(2147483647); // Time travel 23.85 days resolved = await web5did.resolve(did); - expect(resolved.did).to.equal(did); + expect(resolved).to.not.be.undefined; + expect(resolved).to.equal(didData); }); it('should return object with keys undefined if key data not provided', async () => { const did = 'did:ion:abcd1234'; const didData = { connected: true, - did: did, endpoint: 'http://localhost:55500', }; - web5did.register(didData); + await web5did.manager.set(did, didData); const resolved = await web5did.resolve(did); expect(resolved.keys).to.be.undefined; - }); + }); }); }); \ No newline at end of file diff --git a/tests/storage/MemoryStorage.spec.js b/tests/storage/memory-storage.spec.js similarity index 97% rename from tests/storage/MemoryStorage.spec.js rename to tests/storage/memory-storage.spec.js index dda58f188..9d41edbc0 100644 --- a/tests/storage/MemoryStorage.spec.js +++ b/tests/storage/memory-storage.spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; import sinon from 'sinon'; -import { MemoryStorage } from '../../src/storage/MemoryStorage.js'; +import { MemoryStorage } from '../../src/storage/memory-storage.js'; describe('MemoryStorage', async () => { before(function () {