From dd79c5004429706ca932f9365a10679addc5fa07 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Sep 2024 10:50:16 +0000 Subject: [PATCH 1/4] deps: bump @libp2p/logger from 4.0.20 to 5.0.0 Bumps [@libp2p/logger](https://github.com/libp2p/js-libp2p) from 4.0.20 to 5.0.0. - [Release notes](https://github.com/libp2p/js-libp2p/releases) - [Changelog](https://github.com/libp2p/js-libp2p/blob/main/.release-please.json) - [Commits](https://github.com/libp2p/js-libp2p/compare/logger-v4.0.20...utils-v5.0.0) --- updated-dependencies: - dependency-name: "@libp2p/logger" dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- packages/client/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/package.json b/packages/client/package.json index 3149672..b11aa8e 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -46,7 +46,7 @@ }, "dependencies": { "@libp2p/interface": "^1.1.1", - "@libp2p/logger": "^4.0.4", + "@libp2p/logger": "^5.0.0", "@libp2p/peer-id": "^4.0.4", "@multiformats/multiaddr": "^12.1.3", "any-signal": "^4.1.1", From d240ac0c130168b27c860f5abe3f928ef4e067bd Mon Sep 17 00:00:00 2001 From: achingbrain Date: Sun, 15 Sep 2024 12:21:54 +0100 Subject: [PATCH 2/4] chore: update deps --- .github/dependabot.yml | 2 +- README.md | 20 +-- package.json | 91 +------------ packages/client/README.md | 31 +++++ packages/client/package.json | 120 +++++++++++++++--- packages/client/src/client.ts | 57 +++++---- packages/client/src/errors.ts | 17 +++ packages/client/src/index.ts | 8 +- packages/client/src/routings.ts | 36 +++--- packages/client/test/index.spec.ts | 61 ++++----- packages/client/test/routings.spec.ts | 51 ++++---- packages/client/typedoc.json | 4 +- packages/interop/package.json | 116 ++++++++++++++--- packages/interop/test/index.spec.ts | 23 ++-- packages/server/README.md | 14 +- packages/server/package.json | 110 ++++++++++++++-- packages/server/src/constants.ts | 2 + .../server/src/routes/routing/v1/ipns/get.ts | 23 +++- .../server/src/routes/routing/v1/ipns/put.ts | 26 ++-- packages/server/test/index.spec.ts | 37 +++--- packages/server/typedoc.json | 4 +- 21 files changed, 547 insertions(+), 306 deletions(-) create mode 100644 packages/client/src/errors.ts create mode 100644 packages/server/src/constants.ts diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0bc3b42..d401a77 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,7 @@ updates: schedule: interval: daily time: "10:00" - open-pull-requests-limit: 10 + open-pull-requests-limit: 20 commit-message: prefix: "deps" prefix-development: "deps(dev)" diff --git a/README.md b/README.md index 33d217c..91c58f4 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@

+# helia-delegated-routing-v1-http-api + [![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech) [![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech) [![codecov](https://img.shields.io/codecov/c/github/ipfs/helia-delegated-routing-v1-http-api.svg?style=flat-square)](https://codecov.io/gh/ipfs/helia-delegated-routing-v1-http-api) @@ -15,24 +17,24 @@ This repo contains a server implementation of the IPFS [Delegated Routing V1 HTTP API](https://specs.ipfs.tech/routing/http-routing-v1/) along with a client that can be used to interact with any compliant server implementation. -## Packages +# Packages -- [`/packages/client`](./packages/client) A Delegated Routing V1 HTTP API client -- [`/packages/interop`](./packages/interop) Interop tests for the Delegated Routing V1 HTTP API server powered by Helia -- [`/packages/server`](./packages/server) A Delegated Routing V1 HTTP API server powered by Helia +- [`packages/client`](https://github.com/ipfs/helia-delegated-routing-v1-http-api/tree/main/packages/client) A Delegated Routing V1 HTTP API client +- [`packages/interop`](https://github.com/ipfs/helia-delegated-routing-v1-http-api/tree/main/packages/interop) Interop tests for the Delegated Routing V1 HTTP API server powered by Helia +- [`packages/server`](https://github.com/ipfs/helia-delegated-routing-v1-http-api/tree/main/packages/server) A Delegated Routing V1 HTTP API server powered by Helia -## API Docs +# API Docs - -## License +# License Licensed under either of -- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) -- MIT ([LICENSE-MIT](LICENSE-MIT) / ) +- Apache 2.0, ([LICENSE-APACHE](https://github.com/ipfs/helia-delegated-routing-v1-http-api/blob/main/LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](https://github.com/ipfs/helia-delegated-routing-v1-http-api/blob/main/LICENSE-MIT) / ) -## Contribute +# Contribute Contributions welcome! Please check out [the issues](https://github.com/ipfs/helia-delegated-routing-v1-http-api/issues). diff --git a/package.json b/package.json index 39b63cf..610fe7e 100644 --- a/package.json +++ b/package.json @@ -37,95 +37,12 @@ }, "devDependencies": { "aegir": "^44.1.1", - "npm-run-all": "^4.1.5" + "npm-run-all": "^4.1.5", + "patch-package": "^8.0.0", + "rimraf": "^6.0.1" }, "type": "module", "workspaces": [ "packages/*" - ], - "release": { - "branches": [ - "main" - ], - "plugins": [ - [ - "@semantic-release/commit-analyzer", - { - "preset": "conventionalcommits", - "releaseRules": [ - { - "breaking": true, - "release": "major" - }, - { - "revert": true, - "release": "patch" - }, - { - "type": "feat", - "release": "minor" - }, - { - "type": "fix", - "release": "patch" - }, - { - "type": "docs", - "release": "patch" - }, - { - "type": "test", - "release": "patch" - }, - { - "type": "deps", - "release": "patch" - }, - { - "scope": "no-release", - "release": false - } - ] - } - ], - [ - "@semantic-release/release-notes-generator", - { - "preset": "conventionalcommits", - "presetConfig": { - "types": [ - { - "type": "feat", - "section": "Features" - }, - { - "type": "fix", - "section": "Bug Fixes" - }, - { - "type": "chore", - "section": "Trivial Changes" - }, - { - "type": "docs", - "section": "Documentation" - }, - { - "type": "deps", - "section": "Dependencies" - }, - { - "type": "test", - "section": "Tests" - } - ] - } - } - ], - "@semantic-release/changelog", - "@semantic-release/npm", - "@semantic-release/github", - "@semantic-release/git" - ] - } + ] } diff --git a/packages/client/README.md b/packages/client/README.md index bfa616a..772aa39 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -4,6 +4,8 @@

+# @helia/delegated-routing-v1-http-api-client + [![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech) [![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech) [![codecov](https://img.shields.io/codecov/c/github/ipfs/helia-delegated-routing-v1-http-api.svg?style=flat-square)](https://codecov.io/gh/ipfs/helia-delegated-routing-v1-http-api) @@ -50,3 +52,32 @@ const libp2p = await createLibp2p({ // later this will use the configured HTTP gateway await libp2p.peerRouting.findPeer(peerIdFromString('QmFoo')) ``` + +# Install + +```console +$ npm i @helia/delegated-routing-v1-http-api-client +``` + +# API Docs + +- + +# License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](https://github.com/ipfs/helia-delegated-routing-v1-http-api/blob/main/packages/client/LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](https://github.com/ipfs/helia-delegated-routing-v1-http-api/blob/main/packages/client/LICENSE-MIT) / ) + +# Contribute + +Contributions welcome! Please check out [the issues](https://github.com/ipfs/helia-delegated-routing-v1-http-api/issues). + +Also see our [contributing document](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md) for more information on how we work, and about contributing in general. + +Please be aware that all interactions related to this repo are subject to the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + +[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md) diff --git a/packages/client/package.json b/packages/client/package.json index b11aa8e..1a2494b 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -3,7 +3,7 @@ "version": "3.0.1", "description": "A Delegated Routing V1 HTTP API client", "license": "Apache-2.0 OR MIT", - "homepage": "https://github.com/ipfs/helia-delegated-routing-v1-http-api/tree/master/packages/client#readme", + "homepage": "https://github.com/ipfs/helia-delegated-routing-v1-http-api/tree/main/packages/client#readme", "repository": { "type": "git", "url": "git+https://github.com/ipfs/helia-delegated-routing-v1-http-api.git" @@ -11,6 +11,10 @@ "bugs": { "url": "https://github.com/ipfs/helia-delegated-routing-v1-http-api/issues" }, + "publishConfig": { + "access": "public", + "provenance": true + }, "keywords": [ "IPFS" ], @@ -35,6 +39,91 @@ "sourceType": "module" } }, + "release": { + "branches": [ + "main" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, "scripts": { "clean": "aegir clean", "lint": "aegir lint", @@ -45,26 +134,25 @@ "release": "aegir release" }, "dependencies": { - "@libp2p/interface": "^1.1.1", - "@libp2p/logger": "^5.0.0", - "@libp2p/peer-id": "^4.0.4", - "@multiformats/multiaddr": "^12.1.3", + "@libp2p/interface": "^2.0.1", + "@libp2p/logger": "^5.0.1", + "@libp2p/peer-id": "^5.0.1", + "@multiformats/multiaddr": "^12.3.1", "any-signal": "^4.1.1", - "browser-readablestream-to-it": "^2.0.3", - "ipns": "^9.0.0", - "it-first": "^3.0.3", - "it-map": "^3.0.4", - "it-ndjson": "^1.0.4", - "multiformats": "^13.0.0", - "p-defer": "^4.0.0", + "browser-readablestream-to-it": "^2.0.7", + "ipns": "^10.0.0", + "it-first": "^3.0.6", + "it-map": "^3.1.1", + "it-ndjson": "^1.0.7", + "multiformats": "^13.2.3", + "p-defer": "^4.0.1", "p-queue": "^8.0.1", - "uint8arrays": "^5.0.1" + "uint8arrays": "^5.1.0" }, "devDependencies": { - "@libp2p/peer-id-factory": "^4.0.4", "aegir": "^44.1.1", - "body-parser": "^1.20.2", - "it-all": "^3.0.2" + "body-parser": "^1.20.3", + "it-all": "^3.0.6" }, "sideEffects": false } diff --git a/packages/client/src/client.ts b/packages/client/src/client.ts index 706e05d..fb01200 100644 --- a/packages/client/src/client.ts +++ b/packages/client/src/client.ts @@ -1,14 +1,15 @@ -import { contentRoutingSymbol, peerRoutingSymbol, CodeError, setMaxListeners } from '@libp2p/interface' +import { NotFoundError, contentRoutingSymbol, peerRoutingSymbol, setMaxListeners } from '@libp2p/interface' import { logger } from '@libp2p/logger' import { peerIdFromString } from '@libp2p/peer-id' import { multiaddr } from '@multiformats/multiaddr' import { anySignal } from 'any-signal' import toIt from 'browser-readablestream-to-it' -import { unmarshal, type IPNSRecord, marshal, peerIdToRoutingKey } from 'ipns' +import { unmarshalIPNSRecord, type IPNSRecord, marshalIPNSRecord, multihashToIPNSRoutingKey } from 'ipns' import { ipnsValidator } from 'ipns/validator' import { parse as ndjson } from 'it-ndjson' import defer from 'p-defer' import PQueue from 'p-queue' +import { BadResponseError, InvalidRequestError } from './errors.js' import { DelegatedRoutingV1HttpApiClientContentRouting, DelegatedRoutingV1HttpApiClientPeerRouting } from './routings.js' import type { DelegatedRoutingV1HttpApiClient, DelegatedRoutingV1HttpApiClientInit, GetIPNSOptions, PeerRecord } from './index.js' import type { ContentRouting, PeerRouting, AbortOptions, PeerId } from '@libp2p/interface' @@ -93,18 +94,18 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV if (res.status === 404) { // https://specs.ipfs.tech/routing/http-routing-v1/#response-status-codes - // 404 (Not Found): must be returned if no matching records are found. - throw new CodeError('No matching records found.', 'ERR_NOT_FOUND') + // 404 (Not Found): must be returned if no matching records are found + throw new NotFoundError('No matching records found') } if (res.status === 422) { // https://specs.ipfs.tech/routing/http-routing-v1/#response-status-codes - // 422 (Unprocessable Entity): request does not conform to schema or semantic constraints. - throw new CodeError('Request does not conform to schema or semantic constraints.', 'ERR_INVALID_REQUEST') + // 422 (Unprocessable Entity): request does not conform to schema or semantic constraints + throw new InvalidRequestError('Request does not conform to schema or semantic constraints') } if (res.body == null) { - throw new CodeError('Routing response had no body', 'ERR_BAD_RESPONSE') + throw new BadResponseError('Routing response had no body') } const contentType = res.headers.get('Content-Type') @@ -159,17 +160,17 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV if (res.status === 404) { // https://specs.ipfs.tech/routing/http-routing-v1/#response-status-codes // 404 (Not Found): must be returned if no matching records are found. - throw new CodeError('No matching records found.', 'ERR_NOT_FOUND') + throw new NotFoundError('No matching records found') } if (res.status === 422) { // https://specs.ipfs.tech/routing/http-routing-v1/#response-status-codes - // 422 (Unprocessable Entity): request does not conform to schema or semantic constraints. - throw new CodeError('Request does not conform to schema or semantic constraints.', 'ERR_INVALID_REQUEST') + // 422 (Unprocessable Entity): request does not conform to schema or semantic constraints + throw new InvalidRequestError('Request does not conform to schema or semantic constraints') } if (res.body == null) { - throw new CodeError('Routing response had no body', 'ERR_BAD_RESPONSE') + throw new BadResponseError('Routing response had no body') } const contentType = res.headers.get('Content-Type') @@ -199,8 +200,8 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV } } - async getIPNS (peerId: PeerId, options: GetIPNSOptions = {}): Promise { - log('getIPNS starts: %c', peerId) + async getIPNS (libp2pKey: CID, options: GetIPNSOptions = {}): Promise { + log('getIPNS starts: %s', libp2pKey) const timeoutSignal = AbortSignal.timeout(this.timeout) const signal = anySignal([this.shutDownController.signal, timeoutSignal, options.signal]) @@ -214,7 +215,7 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV }) // https://specs.ipfs.tech/routing/http-routing-v1/ - const resource = `${this.clientUrl}routing/v1/ipns/${peerId.toCID().toString()}` + const resource = `${this.clientUrl}routing/v1/ipns/${libp2pKey}` try { await onStart.promise @@ -226,28 +227,28 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV if (res.status === 404) { // https://specs.ipfs.tech/routing/http-routing-v1/#response-status-codes - // 404 (Not Found): must be returned if no matching records are found. - throw new CodeError('No matching records found.', 'ERR_NOT_FOUND') + // 404 (Not Found): must be returned if no matching records are found + throw new NotFoundError('No matching records found') } if (res.status === 422) { // https://specs.ipfs.tech/routing/http-routing-v1/#response-status-codes - // 422 (Unprocessable Entity): request does not conform to schema or semantic constraints. - throw new CodeError('Request does not conform to schema or semantic constraints.', 'ERR_INVALID_REQUEST') + // 422 (Unprocessable Entity): request does not conform to schema or semantic constraints + throw new InvalidRequestError('Request does not conform to schema or semantic constraints') } if (res.body == null) { - throw new CodeError('GET ipns response had no body', 'ERR_BAD_RESPONSE') + throw new BadResponseError('GET ipns response had no body') } const buf = await res.arrayBuffer() const body = new Uint8Array(buf, 0, buf.byteLength) if (options.validate !== false) { - await ipnsValidator(peerIdToRoutingKey(peerId), body) + await ipnsValidator(multihashToIPNSRoutingKey(libp2pKey.multihash), body) } - return unmarshal(body) + return unmarshalIPNSRecord(body) } catch (err: any) { log.error('getIPNS GET %s error:', resource, err) @@ -255,12 +256,12 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV } finally { signal.clear() onFinish.resolve() - log('getIPNS finished: %c', peerId) + log('getIPNS finished: %s', libp2pKey) } } - async putIPNS (peerId: PeerId, record: IPNSRecord, options: AbortOptions = {}): Promise { - log('putIPNS starts: %c', peerId) + async putIPNS (libp2pKey: CID, record: IPNSRecord, options: AbortOptions = {}): Promise { + log('putIPNS starts: %c', libp2pKey) const timeoutSignal = AbortSignal.timeout(this.timeout) const signal = anySignal([this.shutDownController.signal, timeoutSignal, options.signal]) @@ -274,12 +275,12 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV }) // https://specs.ipfs.tech/routing/http-routing-v1/ - const resource = `${this.clientUrl}routing/v1/ipns/${peerId.toCID().toString()}` + const resource = `${this.clientUrl}routing/v1/ipns/${libp2pKey}` try { await onStart.promise - const body = marshal(record) + const body = marshalIPNSRecord(record) const getOptions = { method: 'PUT', headers: { 'Content-Type': 'application/vnd.ipfs.ipns-record' }, body, signal } const res = await fetch(resource, getOptions) @@ -287,7 +288,7 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV log('putIPNS PUT %s %d', resource, res.status) if (res.status !== 200) { - throw new CodeError('PUT ipns response had status other than 200', 'ERR_BAD_RESPONSE') + throw new BadResponseError('PUT ipns response had status other than 200') } } catch (err: any) { log.error('putIPNS PUT %s error:', resource, err.stack) @@ -296,7 +297,7 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV } finally { signal.clear() onFinish.resolve() - log('putIPNS finished: %c', peerId) + log('putIPNS finished: %c', libp2pKey) } } diff --git a/packages/client/src/errors.ts b/packages/client/src/errors.ts new file mode 100644 index 0000000..c0dcbca --- /dev/null +++ b/packages/client/src/errors.ts @@ -0,0 +1,17 @@ +export class InvalidRequestError extends Error { + static name = 'InvalidRequestError' + + constructor (message = 'Invalid request') { + super(message) + this.name = 'InvalidRequestError' + } +} + +export class BadResponseError extends Error { + static name = 'BadResponseError' + + constructor (message = 'Bad response') { + super(message) + this.name = 'BadResponseError' + } +} diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index ca02c4e..7dd4bcf 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -102,14 +102,14 @@ export interface DelegatedRoutingV1HttpApiClient { getPeers(peerId: PeerId, options?: AbortOptions): AsyncGenerator /** - * Returns a promise of a {@link IPNSRecord} for the given {@link PeerId} + * Returns a promise of a {@link IPNSRecord} for the given {@link MultihashDigest} */ - getIPNS(peerId: PeerId, options?: GetIPNSOptions): Promise + getIPNS(libp2pKey: CID, options?: GetIPNSOptions): Promise /** - * Publishes the given {@link IPNSRecord} for the provided {@link PeerId} + * Publishes the given {@link IPNSRecord} for the provided {@link MultihashDigest} */ - putIPNS(peerId: PeerId, record: IPNSRecord, options?: AbortOptions): Promise + putIPNS(libp2pKey: CID, record: IPNSRecord, options?: AbortOptions): Promise /** * Shut down any currently running HTTP requests and clear up any resources diff --git a/packages/client/src/routings.ts b/packages/client/src/routings.ts index 9e16d80..1f2061d 100644 --- a/packages/client/src/routings.ts +++ b/packages/client/src/routings.ts @@ -1,13 +1,11 @@ -import { type ContentRouting, type PeerRouting, type AbortOptions, type PeerId, type PeerInfo } from '@libp2p/interface' -import { CodeError } from '@libp2p/interface' -import { peerIdFromBytes } from '@libp2p/peer-id' -import { marshal, unmarshal } from 'ipns' +import { type ContentRouting, type PeerRouting, type AbortOptions, type PeerId, type PeerInfo, NotFoundError } from '@libp2p/interface' +import { marshalIPNSRecord, multihashFromIPNSRoutingKey, unmarshalIPNSRecord } from 'ipns' import first from 'it-first' import map from 'it-map' +import { CID } from 'multiformats/cid' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import type { DelegatedRoutingV1HttpApiClient } from './index.js' -import type { CID } from 'multiformats/cid' const IPNS_PREFIX = uint8ArrayFromString('/ipns/') @@ -15,10 +13,6 @@ function isIPNSKey (key: Uint8Array): boolean { return uint8ArrayEquals(key.subarray(0, IPNS_PREFIX.byteLength), IPNS_PREFIX) } -const peerIdFromRoutingKey = (key: Uint8Array): PeerId => { - return peerIdFromBytes(key.slice(IPNS_PREFIX.length)) -} - /** * Wrapper class to convert [http-routing-v1 content events](https://specs.ipfs.tech/routing/http-routing-v1/#response-body) into returned values */ @@ -47,28 +41,30 @@ export class DelegatedRoutingV1HttpApiClientContentRouting implements ContentRou return } - const peerId = peerIdFromRoutingKey(key) - const record = unmarshal(value) + const digest = multihashFromIPNSRoutingKey(key) + const cid = CID.createV1(0x72, digest) + const record = unmarshalIPNSRecord(value) - await this.client.putIPNS(peerId, record, options) + await this.client.putIPNS(cid, record, options) } async get (key: Uint8Array, options?: AbortOptions): Promise { if (!isIPNSKey(key)) { - throw new CodeError('Not found', 'ERR_NOT_FOUND') + throw new NotFoundError('Not found') } - const peerId = peerIdFromRoutingKey(key) + const digest = multihashFromIPNSRoutingKey(key) + const cid = CID.createV1(0x72, digest) try { - const record = await this.client.getIPNS(peerId, options) + const record = await this.client.getIPNS(cid, options) - return marshal(record) + return marshalIPNSRecord(record) } catch (err: any) { - // ERR_BAD_RESPONSE is thrown when the response had no body, which means + // BadResponseError is thrown when the response had no body, which means // the record couldn't be found - if (err.code === 'ERR_BAD_RESPONSE') { - throw new CodeError('Not found', 'ERR_NOT_FOUND') + if (err.name === 'BadResponseError') { + throw new NotFoundError('Not found') } throw err @@ -96,7 +92,7 @@ export class DelegatedRoutingV1HttpApiClientPeerRouting implements PeerRouting { } } - throw new CodeError('Not found', 'ERR_NOT_FOUND') + throw new NotFoundError('Not found') } async * getClosestPeers (key: Uint8Array, options: AbortOptions = {}): AsyncIterable { diff --git a/packages/client/test/index.spec.ts b/packages/client/test/index.spec.ts index 9c9fcd2..aa0be1e 100644 --- a/packages/client/test/index.spec.ts +++ b/packages/client/test/index.spec.ts @@ -1,10 +1,10 @@ /* eslint-env mocha */ -import { peerIdFromString } from '@libp2p/peer-id' -import { createEd25519PeerId } from '@libp2p/peer-id-factory' +import { generateKeyPair } from '@libp2p/crypto/keys' +import { peerIdFromPrivateKey, peerIdFromString } from '@libp2p/peer-id' import { multiaddr } from '@multiformats/multiaddr' import { expect } from 'aegir/chai' -import { create as createIpnsRecord, marshal as marshalIpnsRecord } from 'ipns' +import { createIPNSRecord, marshalIPNSRecord } from 'ipns' import all from 'it-all' import { CID } from 'multiformats/cid' import { createDelegatedRoutingV1HttpApiClient, type DelegatedRoutingV1HttpApiClient } from '../src/index.js' @@ -33,16 +33,16 @@ describe('delegated-routing-v1-http-api-client', () => { Protocol: 'transport-bitswap', Schema: 'bitswap', Metadata: 'gBI=', - ID: (await createEd25519PeerId()).toString(), + ID: (await generateKeyPair('Ed25519')).publicKey.toString(), Addrs: ['/ip4/41.41.41.41/tcp/1234'] }, { Protocol: 'transport-bitswap', Schema: 'peer', Metadata: 'gBI=', - ID: (await createEd25519PeerId()).toString(), + ID: (await generateKeyPair('Ed25519')).publicKey.toString(), Addrs: ['/ip4/42.42.42.42/tcp/1234'] }, { - ID: (await createEd25519PeerId()).toString(), + ID: (await generateKeyPair('Ed25519')).publicKey.toString(), Addrs: ['/ip4/43.43.43.43/tcp/1234'] }] @@ -111,35 +111,35 @@ describe('delegated-routing-v1-http-api-client', () => { }) it('should conform records to peer schema', async () => { - const peerId = await createEd25519PeerId() + const privateKey = await generateKeyPair('Ed25519') const records = [{ Protocol: 'transport-bitswap', Schema: 'bitswap', Metadata: 'gBI=', - ID: peerId.toString(), + ID: privateKey.publicKey.toString(), Addrs: ['/ip4/41.41.41.41/tcp/1234'] }, { Protocol: 'transport-saddle', Schema: 'horse-ride', Metadata: 'gBI=', - ID: peerId.toString(), + ID: privateKey.publicKey.toString(), Addrs: ['/ip4/41.41.41.41/tcp/1234'] }, { Protocols: ['transport-bitswap'], Schema: 'peer', Metadata: 'gBI=', - ID: peerId.toString(), + ID: privateKey.publicKey.toString(), Addrs: ['/ip4/42.42.42.42/tcp/1234'] }, { Protocol: 'transport-bitswap', Schema: 'peer', Metadata: 'gBI=', - ID: (await createEd25519PeerId()).toString(), + ID: (await generateKeyPair('Ed25519')).publicKey.toString(), Addrs: ['/ip4/42.42.42.42/tcp/1234'] }, { Schema: 'peer', - ID: (await createEd25519PeerId()).toString() + ID: (await generateKeyPair('Ed25519')).publicKey.toString() }] const peers = [{ @@ -174,12 +174,12 @@ describe('delegated-routing-v1-http-api-client', () => { }] // load peer for the router to fetch - await fetch(`${process.env.ECHO_SERVER}/add-peers/${peerId.toCID().toString()}`, { + await fetch(`${process.env.ECHO_SERVER}/add-peers/${privateKey.publicKey.toCID()}`, { method: 'POST', body: records.map(prov => JSON.stringify(prov)).join('\n') }) - const peerRecords = await all(client.getPeers(peerId)) + const peerRecords = await all(client.getPeers(peerIdFromPrivateKey(privateKey))) expect(peerRecords.map(peerRecord => ({ ...peerRecord, ID: peerRecord.ID.toString(), @@ -193,48 +193,49 @@ describe('delegated-routing-v1-http-api-client', () => { it('should get ipns record', async () => { const cid = CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn') - const peerId = await createEd25519PeerId() - const record = await createIpnsRecord(peerId, cid, 0, 1000) + const privateKey = await generateKeyPair('Ed25519') + const record = await createIPNSRecord(privateKey, cid, 0, 1000) // load record for the router to fetch - await fetch(`${process.env.ECHO_SERVER}/add-ipns/${peerId.toCID().toString()}`, { + await fetch(`${process.env.ECHO_SERVER}/add-ipns/${privateKey.publicKey.toCID()}`, { method: 'POST', headers: { 'Content-Type': 'application/vnd.ipfs.ipns-record' }, - body: marshalIpnsRecord(record) + body: marshalIPNSRecord(record) }) - const ipnsRecord = await client.getIPNS(peerId) - expect(marshalIpnsRecord(ipnsRecord)).to.equalBytes(marshalIpnsRecord(record)) + const ipnsRecord = await client.getIPNS(privateKey.publicKey.toCID()) + expect(marshalIPNSRecord(ipnsRecord)).to.equalBytes(marshalIPNSRecord(record)) }) it('get ipns record fails with bad record', async () => { const cid = CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn') - const peerId = await createEd25519PeerId() - const record = await createIpnsRecord(await createEd25519PeerId(), cid, 0, 1000) + const privateKey = await generateKeyPair('Ed25519') + const otherPrivateKey = await generateKeyPair('Ed25519') + const record = await createIPNSRecord(otherPrivateKey, cid, 0, 1000) // load record for the router to fetch - await fetch(`${process.env.ECHO_SERVER}/add-ipns/${peerId.toCID().toString()}`, { + await fetch(`${process.env.ECHO_SERVER}/add-ipns/${privateKey.publicKey.toCID()}`, { method: 'POST', headers: { 'Content-Type': 'application/vnd.ipfs.ipns-record' }, - body: marshalIpnsRecord(record) + body: marshalIPNSRecord(record) }) - await expect(client.getIPNS(peerId)).to.be.rejected() + await expect(client.getIPNS(privateKey.publicKey.toCID())).to.be.rejected() }) it('should put ipns', async () => { const cid = CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn') - const peerId = await createEd25519PeerId() - const record = await createIpnsRecord(peerId, cid, 0, 1000) + const privateKey = await generateKeyPair('Ed25519') + const record = await createIPNSRecord(privateKey, cid, 0, 1000) - await client.putIPNS(peerId, record) + await client.putIPNS(privateKey.publicKey.toCID(), record) // load record that our client just PUT to remote server - const res = await fetch(`${process.env.ECHO_SERVER}/get-ipns/${peerId.toCID().toString()}`, { + const res = await fetch(`${process.env.ECHO_SERVER}/get-ipns/${privateKey.publicKey.toCID()}`, { method: 'GET', headers: { Accept: 'application/vnd.ipfs.ipns-record' @@ -242,6 +243,6 @@ describe('delegated-routing-v1-http-api-client', () => { }) const receivedRecord = new Uint8Array(await res.arrayBuffer()) - expect(marshalIpnsRecord(record)).to.equalBytes(receivedRecord) + expect(marshalIPNSRecord(record)).to.equalBytes(receivedRecord) }) }) diff --git a/packages/client/test/routings.spec.ts b/packages/client/test/routings.spec.ts index e364861..354c576 100644 --- a/packages/client/test/routings.spec.ts +++ b/packages/client/test/routings.spec.ts @@ -1,10 +1,11 @@ /* eslint-disable max-nested-callbacks */ /* eslint-env mocha */ +import { generateKeyPair } from '@libp2p/crypto/keys' import { contentRoutingSymbol, peerRoutingSymbol } from '@libp2p/interface' -import { createEd25519PeerId } from '@libp2p/peer-id-factory' +import { peerIdFromPrivateKey } from '@libp2p/peer-id' import { expect } from 'aegir/chai' -import { create as createIpnsRecord, marshal as marshalIpnsRecord } from 'ipns' +import { createIPNSRecord, marshalIPNSRecord, multihashToIPNSRoutingKey } from 'ipns' import all from 'it-all' import { CID } from 'multiformats/cid' import { concat as uint8ArrayConcat } from 'uint8arrays/concat' @@ -51,16 +52,16 @@ describe('libp2p content-routing', () => { Protocol: 'transport-bitswap', Schema: 'bitswap', Metadata: 'gBI=', - ID: (await createEd25519PeerId()).toString(), + ID: (await generateKeyPair('Ed25519')).publicKey.toString(), Addrs: ['/ip4/41.41.41.41/tcp/1234'] }, { Protocol: 'transport-bitswap', Schema: 'peer', Metadata: 'gBI=', - ID: (await createEd25519PeerId()).toString(), + ID: (await generateKeyPair('Ed25519')).publicKey.toString(), Addrs: ['/ip4/42.42.42.42/tcp/1234'] }, { - ID: (await createEd25519PeerId()).toString(), + ID: (await generateKeyPair('Ed25519')).publicKey.toString(), Addrs: ['/ip4/43.43.43.43/tcp/1234'] }] @@ -102,17 +103,14 @@ describe('libp2p content-routing', () => { } const cid = CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn') - const peerId = await createEd25519PeerId() - const record = await createIpnsRecord(peerId, cid, 0, 1000) - const key = uint8ArrayConcat([ - uint8ArrayFromString('/ipns/'), - peerId.toBytes() - ]) + const privateKey = await generateKeyPair('Ed25519') + const record = await createIPNSRecord(privateKey, cid, 0, 1000) + const key = multihashToIPNSRoutingKey(privateKey.publicKey.toMultihash()) - await routing.put(key, marshalIpnsRecord(record)) + await routing.put(key, marshalIPNSRecord(record)) // load record that our client just PUT to remote server - const res = await fetch(`${process.env.ECHO_SERVER}/get-ipns/${peerId.toCID().toString()}`, { + const res = await fetch(`${process.env.ECHO_SERVER}/get-ipns/${privateKey.publicKey.toCID()}`, { method: 'GET', headers: { Accept: 'application/vnd.ipfs.ipns-record' @@ -120,7 +118,7 @@ describe('libp2p content-routing', () => { }) const receivedRecord = new Uint8Array(await res.arrayBuffer()) - expect(marshalIpnsRecord(record)).to.equalBytes(receivedRecord) + expect(marshalIPNSRecord(record)).to.equalBytes(receivedRecord) }) it('should not put other records', async () => { @@ -130,10 +128,10 @@ describe('libp2p content-routing', () => { throw new Error('ContentRouting not found') } - const peerId = await createEd25519PeerId() + const privateKey = await generateKeyPair('Ed25519') const key = uint8ArrayConcat([ uint8ArrayFromString('/an-unknown-key/'), - peerId.toBytes() + privateKey.publicKey.toMultihash().bytes ]) await routing.put(key, Uint8Array.from([0, 1, 2, 3, 4])) @@ -149,25 +147,25 @@ describe('libp2p content-routing', () => { } const cid = CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn') - const peerId = await createEd25519PeerId() - const record = await createIpnsRecord(peerId, cid, 0, 1000) + const privateKey = await generateKeyPair('Ed25519') + const record = await createIPNSRecord(privateKey, cid, 0, 1000) // load record for the router to fetch - await fetch(`${process.env.ECHO_SERVER}/add-ipns/${peerId.toCID().toString()}`, { + await fetch(`${process.env.ECHO_SERVER}/add-ipns/${privateKey.publicKey.toCID()}`, { method: 'POST', headers: { 'Content-Type': 'application/vnd.ipfs.ipns-record' }, - body: marshalIpnsRecord(record) + body: marshalIPNSRecord(record) }) const key = uint8ArrayConcat([ uint8ArrayFromString('/ipns/'), - peerId.toBytes() + privateKey.publicKey.toMultihash().bytes ]) const value = await routing.get(key) - expect(value).to.equalBytes(marshalIpnsRecord(record)) + expect(value).to.equalBytes(marshalIPNSRecord(record)) }) it('should not get unknown records', async () => { @@ -177,15 +175,15 @@ describe('libp2p content-routing', () => { throw new Error('ContentRouting not found') } - const peerId = await createEd25519PeerId() + const privateKey = await generateKeyPair('Ed25519') const key = uint8ArrayConcat([ uint8ArrayFromString('/am-unknown-key/'), - peerId.toBytes() + privateKey.publicKey.toMultihash().bytes ]) await expect(routing.get(key)).to.eventually.be.rejected - .with.property('code', 'ERR_NOT_FOUND') + .with.property('name', 'NotFoundError') await expect(getServerCallCount()).to.eventually.equal(0) }) @@ -218,7 +216,8 @@ describe('libp2p peer-routing', () => { throw new Error('PeerRouting not found') } - const peerId = await createEd25519PeerId() + const privateKey = await generateKeyPair('Ed25519') + const peerId = peerIdFromPrivateKey(privateKey) const records = [{ Protocol: 'transport-bitswap', diff --git a/packages/client/typedoc.json b/packages/client/typedoc.json index 627398b..f599dc7 100644 --- a/packages/client/typedoc.json +++ b/packages/client/typedoc.json @@ -1,7 +1,5 @@ { "entryPoints": [ "./src/index.ts" - ], - "readme": "none", - "includeVersion": true + ] } diff --git a/packages/interop/package.json b/packages/interop/package.json index 93bd5c7..7cba6a0 100644 --- a/packages/interop/package.json +++ b/packages/interop/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "description": "Interop tests for the Delegated Routing V1 HTTP API server powered by Helia", "license": "Apache-2.0 OR MIT", - "homepage": "https://github.com/ipfs/helia-delegated-routing-v1-http-api/tree/master/packages/interop#readme", + "homepage": "https://github.com/ipfs/helia-delegated-routing-v1-http-api/tree/main/packages/interop#readme", "repository": { "type": "git", "url": "git+https://github.com/ipfs/helia-delegated-routing-v1-http-api.git" @@ -15,13 +15,6 @@ "IPFS" ], "type": "module", - "types": "./dist/src/index.d.ts", - "files": [ - "src", - "dist", - "!dist/test", - "!**/*.tsbuildinfo" - ], "eslintConfig": { "extends": "ipfs", "parserOptions": { @@ -29,28 +22,113 @@ "sourceType": "module" } }, + "release": { + "branches": [ + "main" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, "scripts": { "clean": "aegir clean", "lint": "aegir lint", "dep-check": "aegir dep-check", "build": "aegir build --bundle false", "test": "aegir test -t node", - "test:node": "aegir test -t node --cov" + "test:node": "aegir test -t node --cov", + "release": "aegir release" }, "devDependencies": { "@helia/delegated-routing-v1-http-api-client": "^3.0.0", "@helia/delegated-routing-v1-http-api-server": "^3.0.0", - "@helia/ipns": "^7.1.0", - "@libp2p/identify": "^2.1.0", - "@libp2p/interface": "^1.1.1", - "@libp2p/kad-dht": "^12.0.3", - "@libp2p/peer-id-factory": "^4.0.4", + "@helia/ipns": "^7.2.3-ec8bf66", + "@libp2p/identify": "^3.0.1", + "@libp2p/interface": "^2.0.1", + "@libp2p/kad-dht": "^13.0.1", "aegir": "^44.1.1", - "fastify": "^4.17.0", - "helia": "^4.0.0", - "ipns": "^9.0.0", - "it-first": "^3.0.3", - "multiformats": "^13.0.0" + "fastify": "^4.28.1", + "helia": "^4.2.6-ec8bf66", + "ipns": "^10.0.0", + "it-first": "^3.0.6", + "multiformats": "^13.2.3" }, "private": true } diff --git a/packages/interop/test/index.spec.ts b/packages/interop/test/index.spec.ts index 7521b16..62a09c9 100644 --- a/packages/interop/test/index.spec.ts +++ b/packages/interop/test/index.spec.ts @@ -3,9 +3,9 @@ import { createDelegatedRoutingV1HttpApiClient } from '@helia/delegated-routing-v1-http-api-client' import { createDelegatedRoutingV1HttpApiServer } from '@helia/delegated-routing-v1-http-api-server' import { ipns } from '@helia/ipns' -import { createEd25519PeerId } from '@libp2p/peer-id-factory' +import { generateKeyPair } from '@libp2p/crypto/keys' import { expect } from 'aegir/chai' -import { create as createIpnsRecord } from 'ipns' +import { createIPNSRecord } from 'ipns' import first from 'it-first' import { CID } from 'multiformats/cid' import * as raw from 'multiformats/codecs/raw' @@ -88,29 +88,30 @@ describe('delegated-routing-v1-http-api interop', () => { expect(result.ID.toString()).to.equal(network[2].libp2p.peerId.toString()) }) - it('should get an IPNS record', async () => { + it.skip('should get an IPNS record', async () => { // publish a record using a remote host const i = ipns(network[5]) const cid = CID.parse('bafybeiczsscdsbs7ffqz55asqdf3smv6klcw3gofszvwlyarci47bgf354') - const peerId = await createEd25519PeerId() - await i.publish(peerId, cid) + const privateKey = await generateKeyPair('Ed25519') + await i.publish(privateKey, cid) // use client to resolve the published record - const record = await client.getIPNS(peerId) + const record = await client.getIPNS(privateKey.publicKey.toCID()) expect(record.value).to.equal(`/ipfs/${cid.toString()}`) }) - it('should put an IPNS record', async () => { + it.skip('should put an IPNS record', async () => { // publish a record using the client const cid = CID.parse('bafybeiczsscdsbs7ffqz55asqdf3smv6klcw3gofszvwlyarci47bgf354') - const peerId = await createEd25519PeerId() - const record = await createIpnsRecord(peerId, cid, 0, 1000 * 60 * 60 * 24) + const privateKey = await generateKeyPair('Ed25519') + const record = await createIPNSRecord(privateKey, cid, 0, 1000 * 60 * 60 * 24) - await client.putIPNS(peerId, record) + await client.putIPNS(privateKey.publicKey.toCID(), record) // resolve the record using a remote host const i = ipns(network[8]) - const result = await i.resolve(peerId) + // @ts-expect-error helia needs to be updated to the latest libp2p deps + const result = await i.resolve(privateKey.publicKey.toCID()) expect(result.cid.toString()).to.equal(cid.toString()) }) }) diff --git a/packages/server/README.md b/packages/server/README.md index 445afac..93c15c4 100644 --- a/packages/server/README.md +++ b/packages/server/README.md @@ -4,6 +4,8 @@

+# @helia/delegated-routing-v1-http-api-server + [![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech) [![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech) [![codecov](https://img.shields.io/codecov/c/github/ipfs/helia-delegated-routing-v1-http-api.svg?style=flat-square)](https://codecov.io/gh/ipfs/helia-delegated-routing-v1-http-api) @@ -63,24 +65,24 @@ await server.listen({ // now make http requests ``` -## Install +# Install ```console $ npm i @helia/delegated-routing-v1-http-api-server ``` -## API Docs +# API Docs - -## License +# License Licensed under either of -- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) -- MIT ([LICENSE-MIT](LICENSE-MIT) / ) +- Apache 2.0, ([LICENSE-APACHE](https://github.com/ipfs/helia-delegated-routing-v1-http-api/blob/main/packages/server/LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](https://github.com/ipfs/helia-delegated-routing-v1-http-api/blob/main/packages/server/LICENSE-MIT) / ) -## Contribute +# Contribute Contributions welcome! Please check out [the issues](https://github.com/ipfs/helia-delegated-routing-v1-http-api/issues). diff --git a/packages/server/package.json b/packages/server/package.json index 27c38b9..efdc146 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -3,7 +3,7 @@ "version": "3.0.3", "description": "A Delegated Routing V1 HTTP API server powered by Helia", "license": "Apache-2.0 OR MIT", - "homepage": "https://github.com/ipfs/helia-delegated-routing-v1-http-api/tree/master/packages/server#readme", + "homepage": "https://github.com/ipfs/helia-delegated-routing-v1-http-api/tree/main/packages/server#readme", "repository": { "type": "git", "url": "git+https://github.com/ipfs/helia-delegated-routing-v1-http-api.git" @@ -11,6 +11,10 @@ "bugs": { "url": "https://github.com/ipfs/helia-delegated-routing-v1-http-api/issues" }, + "publishConfig": { + "access": "public", + "provenance": true + }, "keywords": [ "IPFS" ], @@ -55,6 +59,91 @@ "sourceType": "module" } }, + "release": { + "branches": [ + "main" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, "scripts": { "clean": "aegir clean", "lint": "aegir lint", @@ -66,20 +155,19 @@ }, "dependencies": { "@fastify/cors": "^9.0.1", - "@helia/interface": "^4.0.0", - "@libp2p/interface": "^1.1.1", - "@libp2p/peer-id": "^4.0.4", - "fastify": "^4.17.0", - "ipns": "^9.0.0", - "multiformats": "^13.0.0", + "@helia/interface": "^4.3.1-ec8bf66", + "@libp2p/interface": "^2.0.1", + "@libp2p/peer-id": "^5.0.1", + "fastify": "^4.28.1", + "ipns": "^10.0.0", + "multiformats": "^13.2.3", "raw-body": "^3.0.0" }, "devDependencies": { - "@libp2p/peer-id-factory": "^4.0.4", - "@multiformats/multiaddr": "^12.1.3", - "@types/sinon": "^17.0.0", + "@multiformats/multiaddr": "^12.3.1", + "@types/sinon": "^17.0.3", "aegir": "^44.1.1", - "sinon": "^18.0.0", + "sinon": "^19.0.2", "sinon-ts": "^2.0.0" } } diff --git a/packages/server/src/constants.ts b/packages/server/src/constants.ts new file mode 100644 index 0000000..ba83087 --- /dev/null +++ b/packages/server/src/constants.ts @@ -0,0 +1,2 @@ +// these values are from https://github.com/multiformats/multicodec/blob/master/table.csv +export const LIBP2P_KEY_CODEC = 0x72 \ No newline at end of file diff --git a/packages/server/src/routes/routing/v1/ipns/get.ts b/packages/server/src/routes/routing/v1/ipns/get.ts index 450dcea..c7c8b41 100644 --- a/packages/server/src/routes/routing/v1/ipns/get.ts +++ b/packages/server/src/routes/routing/v1/ipns/get.ts @@ -1,9 +1,9 @@ +import { hasCode } from '@helia/utils' import { setMaxListeners } from '@libp2p/interface' -import { peerIdFromCID } from '@libp2p/peer-id' -import { peerIdToRoutingKey } from 'ipns' +import { multihashToIPNSRoutingKey } from 'ipns' import { CID } from 'multiformats/cid' +import { LIBP2P_KEY_CODEC } from '../../../../constants.js' import type { Helia } from '@helia/interface' -import type { PeerId } from '@libp2p/interface' import type { FastifyInstance } from 'fastify' interface Params { @@ -27,7 +27,7 @@ export default function getIpnsV1 (fastify: FastifyInstance, helia: Helia): void } }, handler: async (request, reply) => { - let peerId: PeerId + let cid: CID const controller = new AbortController() setMaxListeners(Infinity, controller.signal) @@ -38,15 +38,24 @@ export default function getIpnsV1 (fastify: FastifyInstance, helia: Helia): void try { // PeerId must be encoded as a Libp2p-key CID. const { name: cidStr } = request.params - const peerCid = CID.parse(cidStr) - peerId = peerIdFromCID(peerCid) + cid = CID.parse(cidStr) } catch (err) { fastify.log.error('could not parse CID from params', err) return reply.code(422).type('text/html').send('Unprocessable Entity') } try { - const rawRecord = await helia.routing.get(peerIdToRoutingKey(peerId), { + if (!hasCode(cid.multihash, 0x00) && !hasCode(cid.multihash, 0x12)) { + fastify.log.error('CID multihash had incorrect code %d', cid.multihash.code) + return reply.code(422).type('text/html').send('Unprocessable Entity') + } + + if (cid.code !== LIBP2P_KEY_CODEC) { + fastify.log.error('CID had incorrect code %d', cid.code) + return reply.code(422).type('text/html').send('Unprocessable Entity') + } + + const rawRecord = await helia.routing.get(multihashToIPNSRoutingKey(cid.multihash), { signal: controller.signal }) diff --git a/packages/server/src/routes/routing/v1/ipns/put.ts b/packages/server/src/routes/routing/v1/ipns/put.ts index 4f7e144..b53472d 100644 --- a/packages/server/src/routes/routing/v1/ipns/put.ts +++ b/packages/server/src/routes/routing/v1/ipns/put.ts @@ -1,11 +1,11 @@ +import { hasCode } from '@helia/utils' import { setMaxListeners } from '@libp2p/interface' -import { peerIdFromCID } from '@libp2p/peer-id' -import { peerIdToRoutingKey } from 'ipns' +import { multihashToIPNSRoutingKey } from 'ipns' import { ipnsValidator } from 'ipns/validator' import { CID } from 'multiformats/cid' import getRawBody from 'raw-body' +import { LIBP2P_KEY_CODEC } from '../../../../constants.js' import type { Helia } from '@helia/interface' -import type { PeerId } from '@libp2p/interface' import type { FastifyInstance } from 'fastify' interface Params { @@ -35,7 +35,7 @@ export default function putIpnsV1 (fastify: FastifyInstance, helia: Helia): void } }, handler: async (request, reply) => { - let peerId: PeerId + let cid: CID const controller = new AbortController() setMaxListeners(Infinity, controller.signal) @@ -46,18 +46,28 @@ export default function putIpnsV1 (fastify: FastifyInstance, helia: Helia): void try { // PeerId must be encoded as a Libp2p-key CID. const { name: cidStr } = request.params - const peerCid = CID.parse(cidStr) - peerId = peerIdFromCID(peerCid) + cid = CID.parse(cidStr) } catch (err) { fastify.log.error('could not parse CID from params', err) return reply.code(422).type('text/html').send('Unprocessable Entity') } + if (!hasCode(cid.multihash, 0x00) && !hasCode(cid.multihash, 0x12)) { + fastify.log.error('CID multihash had incorrect code %d', cid.multihash.code) + return reply.code(422).type('text/html').send('Unprocessable Entity') + } + + if (cid.code !== LIBP2P_KEY_CODEC) { + fastify.log.error('CID had incorrect code %d', cid.code) + return reply.code(422).type('text/html').send('Unprocessable Entity') + } + // @ts-expect-error request.body does not have a type const body: Uint8Array = request.body - await ipnsValidator(peerIdToRoutingKey(peerId), body) + const routingKey = multihashToIPNSRoutingKey(cid.multihash) + await ipnsValidator(routingKey, body) - await helia.routing.put(peerIdToRoutingKey(peerId), body, { + await helia.routing.put(routingKey, body, { signal: controller.signal }) diff --git a/packages/server/test/index.spec.ts b/packages/server/test/index.spec.ts index 8069e02..f27d1a9 100644 --- a/packages/server/test/index.spec.ts +++ b/packages/server/test/index.spec.ts @@ -1,9 +1,8 @@ /* eslint-env mocha */ -import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { multiaddr } from '@multiformats/multiaddr' import { expect } from 'aegir/chai' -import { create as createIpnsRecord, marshal as marshalIpnsRecord, peerIdToRoutingKey } from 'ipns' +import { createIPNSRecord, marshalIPNSRecord, multihashToIPNSRoutingKey } from 'ipns' import { CID } from 'multiformats' import { stubInterface } from 'sinon-ts' import { createDelegatedRoutingV1HttpApiServer } from '../src/index.js' @@ -11,6 +10,8 @@ import type { Helia } from '@helia/interface' import type { PeerInfo } from '@libp2p/interface' import type { FastifyInstance } from 'fastify' import type { StubbedInstance } from 'sinon-ts' +import { peerIdFromPrivateKey } from '@libp2p/peer-id' +import { generateKeyPair } from '@libp2p/crypto/keys' describe('delegated-routing-v1-http-api-server', () => { let helia: StubbedInstance @@ -90,13 +91,13 @@ describe('delegated-routing-v1-http-api-server', () => { it('GET providers returns providers', async () => { const provider1: PeerInfo = { - id: await createEd25519PeerId(), + id: peerIdFromPrivateKey(await generateKeyPair('Ed25519')), multiaddrs: [ multiaddr('/ip4/123.123.123.123/tcp/123') ] } const provider2: PeerInfo = { - id: await createEd25519PeerId(), + id: peerIdFromPrivateKey(await generateKeyPair('Ed25519')), multiaddrs: [ multiaddr('/ip4/123.123.123.123/tcp/123') ] @@ -125,13 +126,13 @@ describe('delegated-routing-v1-http-api-server', () => { it('GET providers returns provider stream', async () => { const provider1: PeerInfo = { - id: await createEd25519PeerId(), + id: peerIdFromPrivateKey(await generateKeyPair('Ed25519')), multiaddrs: [ multiaddr('/ip4/123.123.123.123/tcp/123') ] } const provider2: PeerInfo = { - id: await createEd25519PeerId(), + id: peerIdFromPrivateKey(await generateKeyPair('Ed25519')), multiaddrs: [ multiaddr('/ip4/123.123.123.123/tcp/123') ] @@ -165,7 +166,7 @@ describe('delegated-routing-v1-http-api-server', () => { }) it('GET peers returns 422 if peer id is not cid', async () => { - const res = await fetch(`${url}routing/v1/peers/${(await createEd25519PeerId()).toString()}`, { + const res = await fetch(`${url}routing/v1/peers/${peerIdFromPrivateKey(await generateKeyPair('Ed25519')).toString()}`, { method: 'GET' }) @@ -182,7 +183,7 @@ describe('delegated-routing-v1-http-api-server', () => { it('GET peers returns peer records for get peers', async () => { const peer: PeerInfo = { - id: await createEd25519PeerId(), + id: peerIdFromPrivateKey(await generateKeyPair('Ed25519')), multiaddrs: [ multiaddr('/ip4/123.123.123.123/tcp/123') ] @@ -204,7 +205,7 @@ describe('delegated-routing-v1-http-api-server', () => { }) it('GET ipns returns 422 if peer id is not cid', async () => { - const res = await fetch(`${url}routing/v1/ipns/${(await createEd25519PeerId()).toString()}`, { + const res = await fetch(`${url}routing/v1/ipns/${peerIdFromPrivateKey(await generateKeyPair('Ed25519')).toString()}`, { method: 'GET' }) @@ -220,12 +221,13 @@ describe('delegated-routing-v1-http-api-server', () => { }) it('GET ipns returns record', async () => { - const peerId = await createEd25519PeerId() + const privateKey = await generateKeyPair('Ed25519') + const peerId = peerIdFromPrivateKey(privateKey) const cid = CID.parse('bafkreifjjcie6lypi6ny7amxnfftagclbuxndqonfipmb64f2km2devei4') - const record = await createIpnsRecord(peerId, cid, 0, 1000) + const record = await createIPNSRecord(privateKey, cid, 0, 1000) helia.routing.get = async function () { - return marshalIpnsRecord(record) + return marshalIPNSRecord(record) } const res = await fetch(`${url}routing/v1/ipns/${peerId.toCID().toString()}`, { @@ -237,14 +239,15 @@ describe('delegated-routing-v1-http-api-server', () => { expect(res.status).to.equal(200) const arrayBuffer = await res.arrayBuffer() - expect(new Uint8Array(arrayBuffer)).to.equalBytes(marshalIpnsRecord(record)) + expect(new Uint8Array(arrayBuffer)).to.equalBytes(marshalIPNSRecord(record)) }) it('PUT ipns puts record', async () => { - const peerId = await createEd25519PeerId() + const privateKey = await generateKeyPair('Ed25519') + const peerId = peerIdFromPrivateKey(privateKey) const cid = CID.parse('bafkreifjjcie6lypi6ny7amxnfftagclbuxndqonfipmb64f2km2devei4') - const record = await createIpnsRecord(peerId, cid, 0, 1000) - const marshalledRecord = marshalIpnsRecord(record) + const record = await createIPNSRecord(privateKey, cid, 0, 1000) + const marshalledRecord = marshalIPNSRecord(record) let putKey: Uint8Array = new Uint8Array() let putValue: Uint8Array = new Uint8Array() @@ -263,7 +266,7 @@ describe('delegated-routing-v1-http-api-server', () => { }) expect(res.status).to.equal(200) - expect(putKey).to.equalBytes(peerIdToRoutingKey(peerId)) + expect(putKey).to.equalBytes(multihashToIPNSRoutingKey(privateKey.publicKey.toMultihash())) expect(putValue).to.equalBytes(marshalledRecord) }) }) diff --git a/packages/server/typedoc.json b/packages/server/typedoc.json index bc4333f..1e561cb 100644 --- a/packages/server/typedoc.json +++ b/packages/server/typedoc.json @@ -2,7 +2,5 @@ "entryPoints": [ "./src/index.ts", "./src/routes/index.ts" - ], - "readme": "none", - "includeVersion": true + ] } From 5ed09a63d2a1be8d88ec92049d356f498c48783a Mon Sep 17 00:00:00 2001 From: achingbrain Date: Sun, 15 Sep 2024 12:25:22 +0100 Subject: [PATCH 3/4] chore: update readme --- packages/client/README.md | 15 +++++++++++++++ packages/server/README.md | 26 ++++++++++++++++++++------ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/client/README.md b/packages/client/README.md index 772aa39..2c7fe09 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -15,6 +15,21 @@ # About + + A client implementation of the IPFS [Delegated Routing V1 HTTP API](https://specs.ipfs.tech/routing/http-routing-v1/) that can be used to interact with any compliant server implementation. ## Example diff --git a/packages/server/README.md b/packages/server/README.md index 93c15c4..068684d 100644 --- a/packages/server/README.md +++ b/packages/server/README.md @@ -13,18 +13,33 @@ > A Delegated Routing V1 HTTP API server powered by Helia -## About +# About + + Implements HTTP routes for a Fastify server that conform to the [Routing V1 HTTP API](https://specs.ipfs.tech/routing/http-routing-v1/). -### Example +## Example ```typescript import { createHelia } from 'helia' -import { createRoutingV1HttpApiServer } from '@helia/routing-v1-http-api-server' +import { createDelegatedRoutingV1HttpApiServer } from '@helia/delegated-routing-v1-http-api-server' const helia = await createHelia() -const server = await createRoutingV1HttpApiServer(helia, { +const server = await createDelegatedRoutingV1HttpApiServer(helia, { listen: { // fastify listen options } @@ -34,9 +49,8 @@ const server = await createRoutingV1HttpApiServer(helia, { ``` Alternatively if you have a Fastify instance already you can add routes to it. -, -### Example +## Example ```typescript import fastify from 'fastify' From 02e50de75f0474c54f7368caf90260a5349c273f Mon Sep 17 00:00:00 2001 From: achingbrain Date: Sun, 15 Sep 2024 12:29:18 +0100 Subject: [PATCH 4/4] chore: linting and deps --- packages/client/package.json | 1 + packages/interop/package.json | 5 +++-- packages/server/package.json | 4 +++- packages/server/src/constants.ts | 2 +- packages/server/src/routes/routing/v1/ipns/get.ts | 8 +++++--- packages/server/test/index.spec.ts | 4 ++-- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index 1a2494b..41cc7a2 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -150,6 +150,7 @@ "uint8arrays": "^5.1.0" }, "devDependencies": { + "@libp2p/crypto": "^5.0.1", "aegir": "^44.1.1", "body-parser": "^1.20.3", "it-all": "^3.0.6" diff --git a/packages/interop/package.json b/packages/interop/package.json index 7cba6a0..1d42a2f 100644 --- a/packages/interop/package.json +++ b/packages/interop/package.json @@ -119,13 +119,14 @@ "devDependencies": { "@helia/delegated-routing-v1-http-api-client": "^3.0.0", "@helia/delegated-routing-v1-http-api-server": "^3.0.0", - "@helia/ipns": "^7.2.3-ec8bf66", + "@helia/ipns": "next", + "@libp2p/crypto": "^5.0.1", "@libp2p/identify": "^3.0.1", "@libp2p/interface": "^2.0.1", "@libp2p/kad-dht": "^13.0.1", "aegir": "^44.1.1", "fastify": "^4.28.1", - "helia": "^4.2.6-ec8bf66", + "helia": "next", "ipns": "^10.0.0", "it-first": "^3.0.6", "multiformats": "^13.2.3" diff --git a/packages/server/package.json b/packages/server/package.json index efdc146..d85b3a7 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -155,7 +155,8 @@ }, "dependencies": { "@fastify/cors": "^9.0.1", - "@helia/interface": "^4.3.1-ec8bf66", + "@helia/interface": "next", + "@helia/utils": "next", "@libp2p/interface": "^2.0.1", "@libp2p/peer-id": "^5.0.1", "fastify": "^4.28.1", @@ -164,6 +165,7 @@ "raw-body": "^3.0.0" }, "devDependencies": { + "@libp2p/crypto": "^5.0.1", "@multiformats/multiaddr": "^12.3.1", "@types/sinon": "^17.0.3", "aegir": "^44.1.1", diff --git a/packages/server/src/constants.ts b/packages/server/src/constants.ts index ba83087..da36f9a 100644 --- a/packages/server/src/constants.ts +++ b/packages/server/src/constants.ts @@ -1,2 +1,2 @@ // these values are from https://github.com/multiformats/multicodec/blob/master/table.csv -export const LIBP2P_KEY_CODEC = 0x72 \ No newline at end of file +export const LIBP2P_KEY_CODEC = 0x72 diff --git a/packages/server/src/routes/routing/v1/ipns/get.ts b/packages/server/src/routes/routing/v1/ipns/get.ts index c7c8b41..c88f9bb 100644 --- a/packages/server/src/routes/routing/v1/ipns/get.ts +++ b/packages/server/src/routes/routing/v1/ipns/get.ts @@ -47,12 +47,12 @@ export default function getIpnsV1 (fastify: FastifyInstance, helia: Helia): void try { if (!hasCode(cid.multihash, 0x00) && !hasCode(cid.multihash, 0x12)) { fastify.log.error('CID multihash had incorrect code %d', cid.multihash.code) - return reply.code(422).type('text/html').send('Unprocessable Entity') + return await reply.code(422).type('text/html').send('Unprocessable Entity') } if (cid.code !== LIBP2P_KEY_CODEC) { fastify.log.error('CID had incorrect code %d', cid.code) - return reply.code(422).type('text/html').send('Unprocessable Entity') + return await reply.code(422).type('text/html').send('Unprocessable Entity') } const rawRecord = await helia.routing.get(multihashToIPNSRoutingKey(cid.multihash), { @@ -64,7 +64,9 @@ export default function getIpnsV1 (fastify: FastifyInstance, helia: Helia): void // one cannot simply send rawRecord https://github.com/fastify/fastify/issues/5118 .send(Buffer.from(rawRecord, 0, rawRecord.byteLength)) } catch (err: any) { - if (err.code === 'ERR_NOT_FOUND' || err.errors?.[0].code === 'ERR_NOT_FOUND') { + if (err.code === 'ERR_NOT_FOUND' || err.errors?.[0].code === 'ERR_NOT_FOUND' || + err.name === 'NotFoundError' || err.errors?.[0].name === 'NotFoundError' + ) { return reply.code(404).send('Record not found') } diff --git a/packages/server/test/index.spec.ts b/packages/server/test/index.spec.ts index f27d1a9..437df0e 100644 --- a/packages/server/test/index.spec.ts +++ b/packages/server/test/index.spec.ts @@ -1,5 +1,7 @@ /* eslint-env mocha */ +import { generateKeyPair } from '@libp2p/crypto/keys' +import { peerIdFromPrivateKey } from '@libp2p/peer-id' import { multiaddr } from '@multiformats/multiaddr' import { expect } from 'aegir/chai' import { createIPNSRecord, marshalIPNSRecord, multihashToIPNSRoutingKey } from 'ipns' @@ -10,8 +12,6 @@ import type { Helia } from '@helia/interface' import type { PeerInfo } from '@libp2p/interface' import type { FastifyInstance } from 'fastify' import type { StubbedInstance } from 'sinon-ts' -import { peerIdFromPrivateKey } from '@libp2p/peer-id' -import { generateKeyPair } from '@libp2p/crypto/keys' describe('delegated-routing-v1-http-api-server', () => { let helia: StubbedInstance