@@ -4,25 +4,19 @@ import { peerIdFromString } from '@libp2p/peer-id'
44import { multiaddr } from '@multiformats/multiaddr'
55import { anySignal } from 'any-signal'
66import toIt from 'browser-readablestream-to-it'
7+ import { unmarshal , type IPNSRecord , marshal } from 'ipns'
8+ import toBuffer from 'it-to-buffer'
79// @ts -expect-error no types
810import ndjson from 'iterable-ndjson'
911import defer from 'p-defer'
1012import PQueue from 'p-queue'
11- import type { RoutingV1HttpApiClient , RoutingV1HttpApiClientInit } from './index.js'
13+ import type { RoutingV1HttpApiClient , RoutingV1HttpApiClientInit , Record } from './index.js'
1214import type { AbortOptions } from '@libp2p/interface'
13- import type { PeerInfo } from '@libp2p/interface/peer-info'
14- import type { Multiaddr } from '@multiformats/multiaddr'
15+ import type { PeerId } from '@libp2p/interface/peer-id'
1516import type { CID } from 'multiformats'
1617
1718const log = logger ( 'routing-v1-http-api-client' )
1819
19- interface RoutingV1HttpApiGetProvidersResponse {
20- Protocol : string
21- Schema : string
22- ID : string
23- Addrs : Multiaddr [ ]
24- }
25-
2620const defaultValues = {
2721 concurrentRequests : 4 ,
2822 timeout : 30e3
@@ -62,7 +56,7 @@ export class DefaultRoutingV1HttpApiClient implements RoutingV1HttpApiClient {
6256 this . started = false
6357 }
6458
65- async * getProviders ( cid : CID , options : AbortOptions | undefined = { } ) : AsyncGenerator < PeerInfo , any , unknown > {
59+ async * getProviders ( cid : CID , options : AbortOptions | undefined = { } ) : AsyncGenerator < Record , any , unknown > {
6660 log ( 'findProviders starts: %c' , cid )
6761
6862 const signal = anySignal ( [ this . shutDownController . signal , options . signal , AbortSignal . timeout ( this . timeout ) ] )
@@ -77,7 +71,7 @@ export class DefaultRoutingV1HttpApiClient implements RoutingV1HttpApiClient {
7771 try {
7872 await onStart . promise
7973
80- // https://github.com/ ipfs/specs/blob/main/ routing/ROUTING_V1_HTTP.md#api
74+ // https://specs. ipfs.tech/routing/http- routing-v1/
8175 const resource = `${ this . clientUrl } routing/v1/providers/${ cid . toString ( ) } `
8276 const getOptions = { headers : { Accept : 'application/x-ndjson' } , signal }
8377 const a = await fetch ( resource , getOptions )
@@ -86,12 +80,23 @@ export class DefaultRoutingV1HttpApiClient implements RoutingV1HttpApiClient {
8680 throw new CodeError ( 'Routing response had no body' , 'ERR_BAD_RESPONSE' )
8781 }
8882
89- for await ( const event of ndjson ( toIt ( a . body ) ) ) {
90- if ( event . Protocol !== 'transport-bitswap' ) {
91- continue
92- }
83+ const contentType = a . headers . get ( 'Content-Type' )
84+ if ( contentType === 'application/json' ) {
85+ const body = await a . json ( )
9386
94- yield this . #mapProvider( event )
87+ for ( const provider of body . Providers ) {
88+ const record = this . #handleRecord( provider )
89+ if ( record !== null ) {
90+ yield record
91+ }
92+ }
93+ } else {
94+ for await ( const provider of ndjson ( toIt ( a . body ) ) ) {
95+ const record = this . #handleRecord( provider )
96+ if ( record !== null ) {
97+ yield record
98+ }
99+ }
95100 }
96101 } catch ( err ) {
97102 log . error ( 'findProviders errored:' , err )
@@ -102,21 +107,141 @@ export class DefaultRoutingV1HttpApiClient implements RoutingV1HttpApiClient {
102107 }
103108 }
104109
105- #mapProvider ( event : RoutingV1HttpApiGetProvidersResponse ) : PeerInfo {
106- const peer = peerIdFromString ( event . ID )
107- const ma : Multiaddr [ ] = [ ]
110+ async * getPeers ( pid : PeerId , options : AbortOptions | undefined = { } ) : AsyncGenerator < Record , any , unknown > {
111+ log ( 'findPeers starts: %c' , pid )
112+
113+ const signal = anySignal ( [ this . shutDownController . signal , options . signal , AbortSignal . timeout ( this . timeout ) ] )
114+ const onStart = defer ( )
115+ const onFinish = defer ( )
116+
117+ void this . httpQueue . add ( async ( ) => {
118+ onStart . resolve ( )
119+ return onFinish . promise
120+ } )
121+
122+ try {
123+ await onStart . promise
124+
125+ // https://specs.ipfs.tech/routing/http-routing-v1/
126+ const resource = `${ this . clientUrl } routing/v1/peers/${ pid . toCID ( ) . toString ( ) } `
127+ const getOptions = { headers : { Accept : 'application/x-ndjson' } , signal }
128+ const a = await fetch ( resource , getOptions )
129+
130+ if ( a . body == null ) {
131+ throw new CodeError ( 'Routing response had no body' , 'ERR_BAD_RESPONSE' )
132+ }
133+
134+ const contentType = a . headers . get ( 'Content-Type' )
135+ if ( contentType === 'application/json' ) {
136+ const body = await a . json ( )
108137
109- for ( const strAddr of event . Addrs ) {
110- const addr = multiaddr ( strAddr )
111- ma . push ( addr )
138+ for ( const peer of body . Peers ) {
139+ const record = this . #handleRecord( peer )
140+ if ( record !== null ) {
141+ yield record
142+ }
143+ }
144+ } else {
145+ for await ( const peer of ndjson ( toIt ( a . body ) ) ) {
146+ const record = this . #handleRecord( peer )
147+ if ( record !== null ) {
148+ yield record
149+ }
150+ }
151+ }
152+ } catch ( err ) {
153+ log . error ( 'findPeers errored:' , err )
154+ } finally {
155+ signal . clear ( )
156+ onFinish . resolve ( )
157+ log ( 'findPeers finished: %c' , pid )
112158 }
159+ }
160+
161+ async getIPNS ( pid : PeerId , options : AbortOptions | undefined = { } ) : Promise < IPNSRecord > {
162+ log ( 'getIPNS starts: %c' , pid )
163+
164+ const signal = anySignal ( [ this . shutDownController . signal , options . signal , AbortSignal . timeout ( this . timeout ) ] )
165+ const onStart = defer ( )
166+ const onFinish = defer ( )
113167
114- const pi = {
115- id : peer ,
116- multiaddrs : ma ,
117- protocols : [ ]
168+ void this . httpQueue . add ( async ( ) => {
169+ onStart . resolve ( )
170+ return onFinish . promise
171+ } )
172+
173+ try {
174+ await onStart . promise
175+
176+ // https://specs.ipfs.tech/routing/http-routing-v1/
177+ const resource = `${ this . clientUrl } routing/v1/ipns/${ pid . toCID ( ) . toString ( ) } `
178+ const getOptions = { headers : { Accept : 'application/vnd.ipfs.ipns-record' } , signal }
179+ const a = await fetch ( resource , getOptions )
180+
181+ if ( a . body == null ) {
182+ throw new CodeError ( 'GET ipns response had no body' , 'ERR_BAD_RESPONSE' )
183+ }
184+
185+ const body = await toBuffer ( toIt ( a . body ) )
186+ return unmarshal ( body )
187+ } finally {
188+ signal . clear ( )
189+ onFinish . resolve ( )
190+ log ( 'getIPNS finished: %c' , pid )
191+ }
192+ }
193+
194+ async putIPNS ( pid : PeerId , record : IPNSRecord , options : AbortOptions | undefined = { } ) : Promise < void > {
195+ log ( 'getIPNS starts: %c' , pid )
196+
197+ const signal = anySignal ( [ this . shutDownController . signal , options . signal , AbortSignal . timeout ( this . timeout ) ] )
198+ const onStart = defer ( )
199+ const onFinish = defer ( )
200+
201+ void this . httpQueue . add ( async ( ) => {
202+ onStart . resolve ( )
203+ return onFinish . promise
204+ } )
205+
206+ try {
207+ await onStart . promise
208+
209+ const body = marshal ( record )
210+
211+ // https://specs.ipfs.tech/routing/http-routing-v1/
212+ const resource = `${ this . clientUrl } routing/v1/ipns/${ pid . toCID ( ) . toString ( ) } `
213+ const getOptions = { method : 'PUT' , headers : { 'Content-Type' : 'application/vnd.ipfs.ipns-record' } , body, signal }
214+ const res = await fetch ( resource , getOptions )
215+ if ( res . status !== 200 ) {
216+ throw new CodeError ( 'PUT ipns response had status other than 200' , 'ERR_BAD_RESPONSE' )
217+ }
218+ } finally {
219+ signal . clear ( )
220+ onFinish . resolve ( )
221+ log ( 'getIPNS finished: %c' , pid )
222+ }
223+ }
224+
225+ #handleRecord ( record : any ) : Record | null {
226+ if ( record . Schema === 'peer' ) {
227+ // Peer schema can have additional, user-defined, fields.
228+ record . ID = peerIdFromString ( record . ID )
229+ record . Addrs = record . Addrs . map ( multiaddr )
230+ return record
231+ } else if ( record . Schema === 'bitswap' ) {
232+ // Bitswap schema cannot have additional fields.
233+ return {
234+ Schema : record . Schema ,
235+ Protocol : record . Protocol ,
236+ ID : peerIdFromString ( record . ID ) ,
237+ Addrs : record . Addrs . map ( multiaddr )
238+ }
239+ } else if ( record . Schema !== '' ) {
240+ // TODO: in Go, we send unknown schemas as an UnknownRecord. I feel like
241+ // doing this here will make it harder. Is there a way in TypeScript
242+ // to do something like if schema === 'bitswap' then it is a BitswapRecord?
118243 }
119244
120- return pi
245+ return null
121246 }
122247}
0 commit comments