Skip to content
This repository was archived by the owner on Oct 30, 2023. It is now read-only.

Commit 5bd026e

Browse files
authored
fix!: use @helia/delegated-routing-v1-http-api-client internally (#27)
Refactors the internal implementation to use the [@helia/delegated-routing-v1-http-api-client](https://www.npmjs.com/package/@helia/delegated-routing-v1-http-api-client) instead of a hand-rolled version. Replaces any older references to "Reframe" with the new name of "Delegated Routing V1 HTTP API". Phew. BREAKING CHANGE: the module has been renamed as the spec has been renamed
1 parent c673145 commit 5bd026e

File tree

4 files changed

+92
-141
lines changed

4 files changed

+92
-141
lines changed

README.md

+21-16
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,49 @@
1-
# @libp2p/http-v1-content-routing <!-- omit in toc -->
1+
# @libp2p/delegated-routing-v1-http-api-content-routing <!-- omit in toc -->
22

33
[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/)
44
[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io)
5-
[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-http-v1-content-routing.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-http-v1-content-routing)
6-
[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-http-v1-content-routing/js-test-and-release.yml?branch=main\&style=flat-square)](https://github.com/libp2p/js-http-v1-content-routing/actions/workflows/js-test-and-release.yml?query=branch%3Amain)
5+
[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-delegated-routing-v1-http-api-content-routing.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-delegated-routing-v1-http-api-content-routing)
6+
[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-delegated-routing-v1-http-api-content-routing/js-test-and-release.yml?branch=main\&style=flat-square)](https://github.com/libp2p/js-delegated-routing-v1-http-api-content-routing/actions/workflows/js-test-and-release.yml?query=branch%3Amain)
77

8-
> Use a Routing V1 HTTP service to discover content providers
8+
> Use a Delegated Routing V1 HTTP service to discover content providers
9+
10+
This is a [ContentRouting](https://libp2p.github.io/js-libp2p/interfaces/_libp2p_interface.content_routing.ContentRouting.html)
11+
implementation that makes use of the [@helia/delegated-routing-v1-http-api-client](https://www.npmjs.com/package/@helia/delegated-routing-v1-http-api-client)
12+
to use servers that implement the snappily-titled [Delegated Routing V1 HTTP API](Delegated Routing V1 HTTP API)
13+
spec to get/put IPNS records and to resolve providers for CIDs.
914

1015
## Table of contents <!-- omit in toc -->
1116

12-
- [Install](#install)
13-
- [Browser `<script>` tag](#browser-script-tag)
17+
- - [Install](#install)
18+
- [Browser `<script>` tag](#browser-script-tag)
1419
- [Example](#example)
15-
- [API Docs](#api-docs)
16-
- [License](#license)
17-
- [Contribution](#contribution)
20+
- [API Docs](#api-docs)
21+
- [License](#license)
22+
- [Contribution](#contribution)
1823

1924
## Install
2025

2126
```console
22-
$ npm i @libp2p/http-v1-content-routing
27+
$ npm i @libp2p/delegated-routing-v1-http-api-content-routing
2328
```
2429

2530
### Browser `<script>` tag
2631

27-
Loading this module through a script tag will make it's exports available as `Libp2pHttpV1ContentRouting` in the global namespace.
32+
Loading this module through a script tag will make it's exports available as `Libp2pDelegatedRoutingV1HttpApiContentRouting` in the global namespace.
2833

2934
```html
30-
<script src="https://unpkg.com/@libp2p/http-v1-content-routing/dist/index.min.js"></script>
35+
<script src="https://unpkg.com/@libp2p/delegated-routing-v1-http-api-content-routing/dist/index.min.js"></script>
3136
```
3237

33-
## Example
38+
# Example
3439

3540
```js
3641
import { createLibp2p } from 'libp2p'
37-
import { reframeContentRouting } from '@libp2p/reframe-content-routing'
42+
import { delgatedRoutingV1HTTPAPIContentRouting } from '@libp2p/delegated-routing-http-v1-content-routing'
3843

3944
const node = await createLibp2p({
4045
contentRouters: [
41-
reframeContentRouting('https://cid.contact/reframe')
46+
delgatedRoutingV1HTTPAPIContentRouting('https://example.org')
4247
]
4348
//.. other config
4449
})
@@ -51,7 +56,7 @@ for await (const provider of node.contentRouting.findProviders('cid')) {
5156

5257
## API Docs
5358

54-
- <https://libp2p.github.io/js-http-v1-content-routing>
59+
- <https://libp2p.github.io/js-delegated-routing-v1-http-api-content-routing>
5560

5661
## License
5762

package.json

+11-14
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
2-
"name": "@libp2p/http-v1-content-routing",
3-
"version": "1.0.2",
4-
"description": "Use a Routing V1 HTTP service to discover content providers",
2+
"name": "@libp2p/delegated-routing-v1-http-api-content-routing",
3+
"version": "0.0.0",
4+
"description": "Use a Delegated Routing V1 HTTP service to discover content providers",
55
"license": "Apache-2.0 OR MIT",
6-
"homepage": "https://github.com/libp2p/js-http-v1-content-routing#readme",
6+
"homepage": "https://github.com/libp2p/js-delegated-routing-v1-http-api-content-routing#readme",
77
"repository": {
88
"type": "git",
9-
"url": "git+https://github.com/libp2p/js-http-v1-content-routing.git"
9+
"url": "git+https://github.com/libp2p/js-delegated-routing-v1-http-api-content-routing.git"
1010
},
1111
"bugs": {
12-
"url": "https://github.com/libp2p/js-http-v1-content-routing/issues"
12+
"url": "https://github.com/libp2p/js-delegated-routing-v1-http-api-content-routing/issues"
1313
},
1414
"type": "module",
1515
"types": "./dist/src/index.d.ts",
@@ -130,20 +130,17 @@
130130
"test:node": "aegir test -t node --cov",
131131
"test:electron-main": "aegir test -t electron-main",
132132
"release": "aegir release",
133-
"docs": "aegir docs"
133+
"docs": "aegir docs -- --includeVersion false"
134134
},
135135
"dependencies": {
136+
"@helia/delegated-routing-v1-http-api-client": "^1.0.1",
136137
"@libp2p/interface": "^0.1.1",
137138
"@libp2p/logger": "^3.0.1",
138139
"@libp2p/peer-id": "^3.0.1",
139-
"@multiformats/multiaddr": "^12.1.2",
140-
"any-signal": "^4.1.1",
141-
"browser-readablestream-to-it": "^2.0.2",
142-
"it-to-buffer": "^4.0.1",
140+
"ipns": "^7.0.1",
141+
"it-map": "^3.0.4",
143142
"multiformats": "^12.0.1",
144-
"p-defer": "^4.0.0",
145-
"p-queue": "^7.3.4",
146-
"uint8arrays": "^4.0.3"
143+
"uint8arrays": "^4.0.6"
147144
},
148145
"devDependencies": {
149146
"@libp2p/peer-id-factory": "^3.0.2",

src/index.ts

+47-100
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,31 @@
1+
import { type DelegatedRoutingV1HttpApiClient, createDelegatedRoutingV1HttpApiClient } from '@helia/delegated-routing-v1-http-api-client'
12
import { CodeError } from '@libp2p/interface/errors'
23
import { logger } from '@libp2p/logger'
3-
import { peerIdFromString } from '@libp2p/peer-id'
4-
import { multiaddr } from '@multiformats/multiaddr'
5-
import { anySignal } from 'any-signal'
6-
import toIt from 'browser-readablestream-to-it'
7-
import toBuffer from 'it-to-buffer'
8-
import defer from 'p-defer'
9-
import PQueue from 'p-queue'
10-
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
4+
import { peerIdFromBytes } from '@libp2p/peer-id'
5+
import { marshal, unmarshal } from 'ipns'
6+
import map from 'it-map'
7+
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
8+
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
119
import type { AbortOptions } from '@libp2p/interface'
1210
import type { ContentRouting } from '@libp2p/interface/content-routing'
11+
import type { PeerId } from '@libp2p/interface/peer-id'
1312
import type { PeerInfo } from '@libp2p/interface/peer-info'
1413
import type { Startable } from '@libp2p/interface/startable'
15-
import type { Multiaddr } from '@multiformats/multiaddr'
1614
import type { CID } from 'multiformats/cid'
1715

18-
const log = logger('reframe-content-routing')
16+
const log = logger('delegated-routing-v1-http-api-content-routing')
1917

20-
export interface ReframeV1Response {
21-
Providers: ReframeV1ResponseItem[]
18+
const IPNS_PREFIX = uint8ArrayFromString('/ipns/')
19+
20+
function isIPNSKey (key: Uint8Array): boolean {
21+
return uint8ArrayEquals(key.subarray(0, IPNS_PREFIX.byteLength), IPNS_PREFIX)
2222
}
2323

24-
export interface ReframeV1ResponseItem {
25-
ID: string
26-
Addrs: string[]
27-
Protocol: string
28-
Schema: string
24+
const peerIdFromRoutingKey = (key: Uint8Array): PeerId => {
25+
return peerIdFromBytes(key.slice(IPNS_PREFIX.length))
2926
}
3027

31-
export interface ReframeContentRoutingInit {
28+
export interface DelegatedRoutingV1HTTPAPIContentRoutingInit {
3229
/**
3330
* A concurrency limit to avoid request flood in web browser (default: 4)
3431
*
@@ -42,33 +39,21 @@ export interface ReframeContentRoutingInit {
4239
timeout?: number
4340
}
4441

45-
const defaultValues = {
46-
concurrentRequests: 4,
47-
timeout: 30e3
48-
}
49-
5042
/**
5143
* An implementation of content routing, using a delegated peer
5244
*/
53-
class ReframeContentRouting implements ContentRouting, Startable {
45+
class DelegatedRoutingV1HTTPAPIContentRouting implements ContentRouting, Startable {
5446
private started: boolean
55-
private readonly httpQueue: PQueue
56-
private readonly shutDownController: AbortController
57-
private readonly clientUrl: URL
58-
private readonly timeout: number
47+
private readonly client: DelegatedRoutingV1HttpApiClient
5948

6049
/**
6150
* Create a new DelegatedContentRouting instance
6251
*/
63-
constructor (url: string | URL, init: ReframeContentRoutingInit = {}) {
52+
constructor (url: string | URL, init: DelegatedRoutingV1HTTPAPIContentRoutingInit = {}) {
6453
this.started = false
65-
this.shutDownController = new AbortController()
66-
this.httpQueue = new PQueue({
67-
concurrency: init.concurrentRequests ?? defaultValues.concurrentRequests
68-
})
69-
this.clientUrl = url instanceof URL ? url : new URL(url)
70-
this.timeout = init.timeout ?? defaultValues.timeout
71-
log('enabled Reframe routing via', url)
54+
this.client = createDelegatedRoutingV1HttpApiClient(new URL(url), init)
55+
56+
log('enabled Delegated Routing V1 HTTP API Content Routing via', url)
7257
}
7358

7459
isStarted (): boolean {
@@ -80,85 +65,47 @@ class ReframeContentRouting implements ContentRouting, Startable {
8065
}
8166

8267
stop (): void {
83-
this.httpQueue.clear()
84-
this.shutDownController.abort()
68+
this.client.stop()
8569
this.started = false
8670
}
8771

88-
async * findProviders (key: CID, options: AbortOptions = {}): AsyncIterable<PeerInfo> {
89-
log('findProviders starts: %c', key)
90-
91-
const signal = anySignal([this.shutDownController.signal, options.signal, AbortSignal.timeout(this.timeout)])
92-
const onStart = defer()
93-
const onFinish = defer()
94-
95-
void this.httpQueue.add(async () => {
96-
onStart.resolve()
97-
return onFinish.promise
98-
})
99-
100-
try {
101-
await onStart.promise
102-
103-
// https://github.com/ipfs/specs/blob/main/routing/ROUTING_V1_HTTP.md#api
104-
const resource = `${this.clientUrl}routing/v1/providers/${key.toString()}`
105-
const getOptions = { headers: { Accept: 'application/x-ndjson' }, signal }
106-
const a = await fetch(resource, getOptions)
107-
108-
if (a.body == null) {
109-
throw new CodeError('Reframe response had no body', 'ERR_BAD_RESPONSE')
72+
async * findProviders (cid: CID, options: AbortOptions = {}): AsyncIterable<PeerInfo> {
73+
yield * map(this.client.getProviders(cid, options), (record) => {
74+
return {
75+
id: record.ID,
76+
multiaddrs: record.Addrs ?? [],
77+
protocols: []
11078
}
111-
112-
const body = await toBuffer(toIt(a.body))
113-
const result: ReframeV1Response = JSON.parse(uint8ArrayToString(body))
114-
115-
for await (const event of result.Providers) {
116-
if (event.Protocol !== 'transport-bitswap' || event.Schema !== 'bitswap') {
117-
continue
118-
}
119-
120-
yield this.mapEvent(event)
121-
}
122-
} catch (err) {
123-
log.error('findProviders errored:', err)
124-
} finally {
125-
signal.clear()
126-
onFinish.resolve()
127-
log('findProviders finished: %c', key)
128-
}
79+
})
12980
}
13081

131-
private mapEvent (event: ReframeV1ResponseItem): PeerInfo {
132-
const peer = peerIdFromString(event.ID)
133-
const ma: Multiaddr[] = []
82+
async provide (): Promise<void> {
83+
// noop
84+
}
13485

135-
for (const strAddr of event.Addrs) {
136-
const addr = multiaddr(strAddr)
137-
ma.push(addr)
86+
async put (key: Uint8Array, value: Uint8Array, options?: AbortOptions): Promise<void> {
87+
if (!isIPNSKey(key)) {
88+
return
13889
}
13990

140-
const pi = {
141-
id: peer,
142-
multiaddrs: ma,
143-
protocols: []
144-
}
91+
const peerId = peerIdFromRoutingKey(key)
92+
const record = unmarshal(value)
14593

146-
return pi
94+
await this.client.putIPNS(peerId, record, options)
14795
}
14896

149-
async provide (): Promise<void> {
150-
// noop
151-
}
97+
async get (key: Uint8Array, options?: AbortOptions): Promise<Uint8Array> {
98+
if (!isIPNSKey(key)) {
99+
throw new CodeError('Not found', 'ERR_NOT_FOUND')
100+
}
152101

153-
async put (): Promise<void> {
154-
// noop
155-
}
102+
const peerId = peerIdFromRoutingKey(key)
103+
const record = await this.client.getIPNS(peerId, options)
156104

157-
async get (): Promise<Uint8Array> {
158-
throw new CodeError('Not found', 'ERR_NOT_FOUND')
105+
return marshal(record)
159106
}
160107
}
161108

162-
export function reframeContentRouting (url: string | URL, init: ReframeContentRoutingInit = {}): () => ContentRouting {
163-
return () => new ReframeContentRouting(url, init)
109+
export function delgatedRoutingV1HTTPAPIContentRouting (url: string | URL, init: DelegatedRoutingV1HTTPAPIContentRoutingInit = {}): () => ContentRouting {
110+
return () => new DelegatedRoutingV1HTTPAPIContentRouting(url, init)
164111
}

test/index.spec.ts

+13-11
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { createEd25519PeerId } from '@libp2p/peer-id-factory'
44
import { expect } from 'aegir/chai'
55
import all from 'it-all'
66
import { CID } from 'multiformats/cid'
7-
import { type ReframeV1ResponseItem, reframeContentRouting } from '../src/index.js'
7+
import { delgatedRoutingV1HTTPAPIContentRouting } from '../src/index.js'
88

99
if (process.env.ECHO_SERVER == null) {
1010
throw new Error('Echo server not configured correctly')
@@ -15,27 +15,29 @@ const cid = CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn')
1515

1616
describe('ReframeContentRouting', function () {
1717
it('should find providers', async () => {
18-
const providers: ReframeV1ResponseItem[] = [{
19-
Protocol: 'transport-bitswap',
20-
Schema: 'bitswap',
18+
const providers = [{
19+
Protocols: ['transport-bitswap'],
20+
Schema: 'peer',
2121
ID: (await createEd25519PeerId()).toString(),
2222
Addrs: ['/ip4/41.41.41.41/tcp/1234']
2323
}, {
2424
Protocol: 'transport-bitswap',
2525
Schema: 'bitswap',
2626
ID: (await createEd25519PeerId()).toString(),
2727
Addrs: ['/ip4/42.42.42.42/tcp/1234']
28+
}, {
29+
Schema: 'unknown',
30+
ID: (await createEd25519PeerId()).toString(),
31+
Addrs: ['/ip4/42.42.42.42/tcp/1234']
2832
}]
2933

3034
// load providers for the router to fetch
3135
await fetch(`${process.env.ECHO_SERVER}/add-providers/${cid.toString()}`, {
3236
method: 'POST',
33-
body: JSON.stringify({
34-
Providers: providers
35-
})
37+
body: providers.map(prov => JSON.stringify(prov)).join('\n')
3638
})
3739

38-
const routing = reframeContentRouting(serverUrl)()
40+
const routing = delgatedRoutingV1HTTPAPIContentRouting(serverUrl)()
3941

4042
const provs = await all(routing.findProviders(cid))
4143
expect(provs.map(prov => ({
@@ -54,7 +56,7 @@ describe('ReframeContentRouting', function () {
5456
body: 'not json'
5557
})
5658

57-
const routing = reframeContentRouting(serverUrl)()
59+
const routing = delgatedRoutingV1HTTPAPIContentRouting(serverUrl)()
5860

5961
const provs = await all(routing.findProviders(cid))
6062
expect(provs).to.be.empty()
@@ -84,14 +86,14 @@ describe('ReframeContentRouting', function () {
8486
})
8587
})
8688

87-
const routing = reframeContentRouting(serverUrl)()
89+
const routing = delgatedRoutingV1HTTPAPIContentRouting(serverUrl)()
8890

8991
const provs = await all(routing.findProviders(cid))
9092
expect(provs).to.be.empty()
9193
})
9294

9395
it('should handle empty input', async () => {
94-
const routing = reframeContentRouting(serverUrl)()
96+
const routing = delgatedRoutingV1HTTPAPIContentRouting(serverUrl)()
9597

9698
const provs = await all(routing.findProviders(cid))
9799
expect(provs).to.be.empty()

0 commit comments

Comments
 (0)