@@ -4,25 +4,19 @@ import { peerIdFromString } from '@libp2p/peer-id'
4
4
import { multiaddr } from '@multiformats/multiaddr'
5
5
import { anySignal } from 'any-signal'
6
6
import toIt from 'browser-readablestream-to-it'
7
+ import { unmarshal , type IPNSRecord , marshal } from 'ipns'
8
+ import toBuffer from 'it-to-buffer'
7
9
// @ts -expect-error no types
8
10
import ndjson from 'iterable-ndjson'
9
11
import defer from 'p-defer'
10
12
import PQueue from 'p-queue'
11
- import type { RoutingV1HttpApiClient , RoutingV1HttpApiClientInit } from './index.js'
13
+ import type { RoutingV1HttpApiClient , RoutingV1HttpApiClientInit , Record } from './index.js'
12
14
import 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'
15
16
import type { CID } from 'multiformats'
16
17
17
18
const log = logger ( 'routing-v1-http-api-client' )
18
19
19
- interface RoutingV1HttpApiGetProvidersResponse {
20
- Protocol : string
21
- Schema : string
22
- ID : string
23
- Addrs : Multiaddr [ ]
24
- }
25
-
26
20
const defaultValues = {
27
21
concurrentRequests : 4 ,
28
22
timeout : 30e3
@@ -62,7 +56,7 @@ export class DefaultRoutingV1HttpApiClient implements RoutingV1HttpApiClient {
62
56
this . started = false
63
57
}
64
58
65
- async * getProviders ( cid : CID , options : AbortOptions | undefined = { } ) : AsyncGenerator < PeerInfo , any , unknown > {
59
+ async * getProviders ( cid : CID , options : AbortOptions | undefined = { } ) : AsyncGenerator < Record , any , unknown > {
66
60
log ( 'findProviders starts: %c' , cid )
67
61
68
62
const signal = anySignal ( [ this . shutDownController . signal , options . signal , AbortSignal . timeout ( this . timeout ) ] )
@@ -77,7 +71,7 @@ export class DefaultRoutingV1HttpApiClient implements RoutingV1HttpApiClient {
77
71
try {
78
72
await onStart . promise
79
73
80
- // https://github.com/ ipfs/specs/blob/main/ routing/ROUTING_V1_HTTP.md#api
74
+ // https://specs. ipfs.tech/routing/http- routing-v1/
81
75
const resource = `${ this . clientUrl } routing/v1/providers/${ cid . toString ( ) } `
82
76
const getOptions = { headers : { Accept : 'application/x-ndjson' } , signal }
83
77
const a = await fetch ( resource , getOptions )
@@ -86,12 +80,23 @@ export class DefaultRoutingV1HttpApiClient implements RoutingV1HttpApiClient {
86
80
throw new CodeError ( 'Routing response had no body' , 'ERR_BAD_RESPONSE' )
87
81
}
88
82
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 ( )
93
86
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
+ }
95
100
}
96
101
} catch ( err ) {
97
102
log . error ( 'findProviders errored:' , err )
@@ -102,21 +107,141 @@ export class DefaultRoutingV1HttpApiClient implements RoutingV1HttpApiClient {
102
107
}
103
108
}
104
109
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 ( )
108
137
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 )
112
158
}
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 ( )
113
167
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?
118
243
}
119
244
120
- return pi
245
+ return null
121
246
}
122
247
}
0 commit comments