Skip to content

Commit c5bc7fb

Browse files
committed
feat: add reframev1 routing support
1 parent a7d8aa6 commit c5bc7fb

File tree

1 file changed

+147
-0
lines changed

1 file changed

+147
-0
lines changed

src/reframev1-routing.tsx

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { logger } from '@libp2p/logger'
2+
import PQueue from 'p-queue'
3+
import defer from 'p-defer'
4+
import errCode from 'err-code'
5+
import anySignal from 'any-signal'
6+
import type { AbortOptions } from '@libp2p/interfaces'
7+
import type { ContentRouting } from '@libp2p/interface-content-routing'
8+
import type { PeerInfo } from '@libp2p/interface-peer-info'
9+
import type { Startable } from '@libp2p/interfaces/startable'
10+
import type { CID } from 'multiformats/cid'
11+
import HTTP from 'ipfs-utils/src/http.js'
12+
import { peerIdFromString } from '@libp2p/peer-id'
13+
import { multiaddr } from '@multiformats/multiaddr'
14+
15+
const log = logger('libp2p:delegated-content-routing')
16+
17+
const DEFAULT_TIMEOUT = 30e3 // 30 second default
18+
const CONCURRENT_HTTP_REQUESTS = 4
19+
20+
export interface HTTPClientExtraOptions {
21+
headers?: Record<string, string>
22+
searchParams?: URLSearchParams
23+
}
24+
25+
/**
26+
* An implementation of content routing, using a delegated peer
27+
*/
28+
class ReframeV1Routing implements ContentRouting, Startable {
29+
private readonly clientUrl: URL
30+
31+
private readonly httpQueue: PQueue
32+
private started: boolean
33+
private abortController: AbortController
34+
35+
/**
36+
* Create a new DelegatedContentRouting instance
37+
*/
38+
constructor (protocol: string, host: string, port: string) {
39+
this.started = false
40+
this.abortController = new AbortController()
41+
42+
// limit concurrency to avoid request flood in web browser
43+
// https://github.com/libp2p/js-libp2p-delegated-content-routing/issues/12
44+
this.httpQueue = new PQueue({
45+
concurrency: CONCURRENT_HTTP_REQUESTS
46+
})
47+
48+
log(`enabled IPNI routing via ${protocol}://${host}:${port}`)
49+
this.clientUrl = new URL(`${protocol}://${host}:${port}`)
50+
}
51+
52+
isStarted (): boolean {
53+
return this.started
54+
}
55+
56+
start (): void {
57+
this.started = true
58+
}
59+
60+
stop (): void {
61+
this.httpQueue.clear()
62+
this.abortController.abort()
63+
this.abortController = new AbortController()
64+
this.started = false
65+
}
66+
67+
/**
68+
* Search the dht for providers of the given CID.
69+
*
70+
* - call `findProviders` on the delegated node.
71+
*/
72+
async * findProviders (key: CID, options: HTTPClientExtraOptions & AbortOptions = {}): AsyncIterable<PeerInfo> {
73+
log('findProviders starts: %c', key)
74+
options.signal = anySignal([this.abortController.signal].concat((options.signal != null) ? [options.signal] : []))
75+
setTimeout(() => {
76+
this.abortController.abort("findProviders timed out")
77+
}, DEFAULT_TIMEOUT);
78+
79+
const onStart = defer()
80+
const onFinish = defer()
81+
82+
void this.httpQueue.add(async () => {
83+
onStart.resolve()
84+
return await onFinish.promise
85+
})
86+
87+
try {
88+
await onStart.promise
89+
90+
const resource = `${this.clientUrl}routing/v1/providers/${key.toString()}`
91+
const getOptions = { headers : { "Accept" : "application/x-ndjson"}, signal : this.abortController.signal}
92+
const a = await HTTP.get(resource, getOptions)
93+
const b = a.ndjson()
94+
for await (const event of b) {
95+
if (event.Protocol != "transport-bitswap" || event.Schema != "bitswap") {
96+
continue
97+
}
98+
99+
console.log(event)
100+
yield this.mapEvent(event)
101+
}
102+
} catch (err) {
103+
log.error('findProviders errored:', err)
104+
throw err
105+
} finally {
106+
onFinish.resolve()
107+
log('findProviders finished: %c', key)
108+
}
109+
}
110+
111+
mapEvent (event: any) : any {
112+
const peer = peerIdFromString(event.ID)
113+
let ma : any = []
114+
for (const strAddr of event.Addrs) {
115+
const addr = multiaddr(strAddr)
116+
ma.push(addr)
117+
}
118+
const pi = {
119+
id: peer,
120+
multiaddrs: ma
121+
}
122+
return pi
123+
}
124+
125+
/**
126+
* Does nothing, just part of implementing the interface
127+
*/
128+
async provide (key: CID, options: HTTPClientExtraOptions & AbortOptions = {}): Promise<void> {
129+
}
130+
131+
/**
132+
* Does nothing, just part of implementing the interface
133+
*/
134+
async put (key: Uint8Array, value: Uint8Array, options: HTTPClientExtraOptions & AbortOptions = {}): Promise<void> {
135+
}
136+
137+
/**
138+
* Does nothing, just part of implementing the interface
139+
*/
140+
async get (key: Uint8Array, options: HTTPClientExtraOptions & AbortOptions = {}): Promise<Uint8Array> {
141+
throw errCode(new Error('Not found'), 'ERR_NOT_FOUND')
142+
}
143+
}
144+
145+
export function reframeV1Routing (protocol :string, host: string, port: string): (components?: any) => ContentRouting {
146+
return () => new ReframeV1Routing(protocol, host, port)
147+
}

0 commit comments

Comments
 (0)