Skip to content

Commit 21253d0

Browse files
committed
feat: add protocol&addr filtering to client
1 parent ec3b1d3 commit 21253d0

File tree

3 files changed

+120
-10
lines changed

3 files changed

+120
-10
lines changed

packages/client/src/client.ts

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import defer from 'p-defer'
1111
import PQueue from 'p-queue'
1212
import { BadResponseError, InvalidRequestError } from './errors.js'
1313
import { DelegatedRoutingV1HttpApiClientContentRouting, DelegatedRoutingV1HttpApiClientPeerRouting } from './routings.js'
14-
import type { DelegatedRoutingV1HttpApiClient, DelegatedRoutingV1HttpApiClientInit, GetIPNSOptions, PeerRecord } from './index.js'
14+
import type { DelegatedRoutingV1HttpApiClient, DelegatedRoutingV1HttpApiClientInit, GetProvidersOptions, GetPeersOptions, GetIPNSOptions, PeerRecord } from './index.js'
1515
import type { ContentRouting, PeerRouting, AbortOptions, PeerId } from '@libp2p/interface'
1616
import type { Multiaddr } from '@multiformats/multiaddr'
1717
import type { CID } from 'multiformats'
@@ -31,6 +31,8 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV
3131
private readonly timeout: number
3232
private readonly contentRouting: ContentRouting
3333
private readonly peerRouting: PeerRouting
34+
private readonly filterAddrs: string[] | undefined
35+
private readonly filterProtocols: string[] | undefined
3436

3537
/**
3638
* Create a new DelegatedContentRouting instance
@@ -44,6 +46,8 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV
4446
})
4547
this.clientUrl = url instanceof URL ? url : new URL(url)
4648
this.timeout = init.timeout ?? defaultValues.timeout
49+
this.filterAddrs = init.filterAddrs
50+
this.filterProtocols = init.filterProtocols
4751
this.contentRouting = new DelegatedRoutingV1HttpApiClientContentRouting(this)
4852
this.peerRouting = new DelegatedRoutingV1HttpApiClientPeerRouting(this)
4953
}
@@ -70,7 +74,7 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV
7074
this.started = false
7175
}
7276

73-
async * getProviders (cid: CID, options: AbortOptions = {}): AsyncGenerator<PeerRecord> {
77+
async * getProviders (cid: CID, options: GetProvidersOptions = {}): AsyncGenerator<PeerRecord> {
7478
log('getProviders starts: %c', cid)
7579

7680
const timeoutSignal = AbortSignal.timeout(this.timeout)
@@ -88,9 +92,22 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV
8892
await onStart.promise
8993

9094
// https://specs.ipfs.tech/routing/http-routing-v1/
91-
const resource = `${this.clientUrl}routing/v1/providers/${cid.toString()}`
95+
const url = new URL(`${this.clientUrl}routing/v1/providers/${cid.toString()}`)
96+
// IPIP-484 filtering. options takes precedence over global filter
97+
if (options.filterAddrs != null || this.filterAddrs != null) {
98+
const filterAddrs = options.filterAddrs?.join(',') ?? this.filterAddrs?.join(',') ?? ''
99+
if (filterAddrs !== '') {
100+
url.searchParams.set('filter-addrs', filterAddrs)
101+
}
102+
}
103+
if (options.filterProtocols != null || this.filterProtocols != null) {
104+
const filterProtocols = options.filterProtocols?.join(',') ?? this.filterProtocols?.join(',') ?? ''
105+
if (filterProtocols !== '') {
106+
url.searchParams.set('filter-protocols', filterProtocols)
107+
}
108+
}
92109
const getOptions = { headers: { Accept: 'application/x-ndjson' }, signal }
93-
const res = await fetch(resource, getOptions)
110+
const res = await fetch(url, getOptions)
94111

95112
if (res.status === 404) {
96113
// https://specs.ipfs.tech/routing/http-routing-v1/#response-status-codes
@@ -135,7 +152,7 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV
135152
}
136153
}
137154

138-
async * getPeers (peerId: PeerId, options: AbortOptions | undefined = {}): AsyncGenerator<PeerRecord> {
155+
async * getPeers (peerId: PeerId, options: GetPeersOptions = {}): AsyncGenerator<PeerRecord> {
139156
log('getPeers starts: %c', peerId)
140157

141158
const timeoutSignal = AbortSignal.timeout(this.timeout)
@@ -153,9 +170,23 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV
153170
await onStart.promise
154171

155172
// https://specs.ipfs.tech/routing/http-routing-v1/
156-
const resource = `${this.clientUrl}routing/v1/peers/${peerId.toCID().toString()}`
173+
const url = new URL(`${this.clientUrl}routing/v1/peers/${peerId.toCID().toString()}`)
174+
175+
// IPIP-484 filtering. local options filter precedence over global filter
176+
if (options.filterAddrs != null || this.filterAddrs != null) {
177+
const filterAddrs = options.filterAddrs?.join(',') ?? this.filterAddrs?.join(',') ?? ''
178+
if (filterAddrs !== '') {
179+
url.searchParams.set('filter-addrs', filterAddrs)
180+
}
181+
}
182+
if (options.filterProtocols != null || this.filterProtocols != null) {
183+
const filterProtocols = options.filterProtocols?.join(',') ?? this.filterProtocols?.join(',') ?? ''
184+
if (filterProtocols !== '') {
185+
url.searchParams.set('filter-protocols', filterProtocols)
186+
}
187+
}
157188
const getOptions = { headers: { Accept: 'application/x-ndjson' }, signal }
158-
const res = await fetch(resource, getOptions)
189+
const res = await fetch(url, getOptions)
159190

160191
if (res.status === 404) {
161192
// https://specs.ipfs.tech/routing/http-routing-v1/#response-status-codes

packages/client/src/index.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,26 @@ export interface PeerRecord {
6464
Protocols: string[]
6565
}
6666

67-
export interface DelegatedRoutingV1HttpApiClientInit {
67+
export interface FilterOptions {
68+
/**
69+
* List of protocols to filter in the PeerRecords as defined in IPIP-484
70+
* If undefined, PeerRecords are not filtered by protocol
71+
*
72+
* @see https://github.com/ipfs/specs/pull/484
73+
* @default undefined
74+
*/
75+
filterProtocols?: string[]
76+
77+
/**
78+
* Array of address filters to filter PeerRecords's addresses as defined in IPIP-484
79+
* If undefined, PeerRecords are not filtered by address
80+
*
81+
* @see https://github.com/ipfs/specs/pull/484
82+
* @default undefined
83+
*/
84+
filterAddrs?: string[]
85+
}
86+
export interface DelegatedRoutingV1HttpApiClientInit extends FilterOptions {
6887
/**
6988
* A concurrency limit to avoid request flood in web browser (default: 4)
7089
*
@@ -88,18 +107,21 @@ export interface GetIPNSOptions extends AbortOptions {
88107
validate?: boolean
89108
}
90109

110+
export type GetProvidersOptions = FilterOptions & AbortOptions
111+
export type GetPeersOptions = FilterOptions & AbortOptions
112+
91113
export interface DelegatedRoutingV1HttpApiClient {
92114
/**
93115
* Returns an async generator of {@link PeerRecord}s that can provide the
94116
* content for the passed {@link CID}
95117
*/
96-
getProviders(cid: CID, options?: AbortOptions): AsyncGenerator<PeerRecord>
118+
getProviders(cid: CID, options?: GetProvidersOptions): AsyncGenerator<PeerRecord>
97119

98120
/**
99121
* Returns an async generator of {@link PeerRecord}s for the provided
100122
* {@link PeerId}
101123
*/
102-
getPeers(peerId: PeerId, options?: AbortOptions): AsyncGenerator<PeerRecord>
124+
getPeers(peerId: PeerId, options?: GetPeersOptions): AsyncGenerator<PeerRecord>
103125

104126
/**
105127
* Returns a promise of a {@link IPNSRecord} for the given {@link MultihashDigest}

packages/client/test/index.spec.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,63 @@ describe('delegated-routing-v1-http-api-client', () => {
6464
})))
6565
})
6666

67+
it('should add filter parameters the query of the request url', async () => {
68+
const providers = [{
69+
Protocol: 'transport-bitswap',
70+
Schema: 'bitswap',
71+
Metadata: 'gBI=',
72+
ID: (await generateKeyPair('Ed25519')).publicKey.toString(),
73+
Addrs: []
74+
}, {
75+
Protocol: 'transport-bitswap',
76+
Schema: 'peer',
77+
Metadata: 'gBI=',
78+
ID: (await generateKeyPair('Ed25519')).publicKey.toString(),
79+
Addrs: ['/ip4/42.42.42.42/tcp/1234']
80+
}, {
81+
ID: (await generateKeyPair('Ed25519')).publicKey.toString(),
82+
Addrs: []
83+
}]
84+
85+
const cid = CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn')
86+
87+
// load providers for the router to fetch
88+
await fetch(`${process.env.ECHO_SERVER}/add-providers/${cid.toString()}`, {
89+
method: 'POST',
90+
body: providers.map(prov => JSON.stringify(prov)).join('\n')
91+
})
92+
93+
await all(client.getProviders(cid, { filterProtocols: ['transport-bitswap', 'unknown'], filterAddrs: ['webtransport', '!p2p-circuit'] }))
94+
95+
// Check if the correct URL was called with filter parameters
96+
const lastCalledUrl = await fetch(`${process.env.ECHO_SERVER}/last-called-url`)
97+
const lastCalledUrlText = await lastCalledUrl.text()
98+
99+
const searchParams = new URLSearchParams(lastCalledUrlText.split('?')[1])
100+
101+
expect(searchParams.get('filter-protocols')).to.equal('transport-bitswap,unknown')
102+
expect(searchParams.get('filter-addrs')).to.equal('webtransport,!p2p-circuit')
103+
})
104+
105+
it('should add filter parameters the query of the request url based on global filter', async () => {
106+
const client = createDelegatedRoutingV1HttpApiClient(new URL(serverUrl), {
107+
filterProtocols: ['transport-bitswap', 'unknown'],
108+
filterAddrs: ['tcp', '!p2p-circuit']
109+
})
110+
const cid = CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn')
111+
112+
await all(client.getProviders(cid))
113+
114+
// Check if the correct URL was called with filter parameters
115+
const lastCalledUrl = await fetch(`${process.env.ECHO_SERVER}/last-called-url`)
116+
const lastCalledUrlText = await lastCalledUrl.text()
117+
118+
const searchParams = new URLSearchParams(lastCalledUrlText.split('?')[1])
119+
120+
expect(searchParams.get('filter-protocols')).to.equal('transport-bitswap,unknown')
121+
expect(searchParams.get('filter-addrs')).to.equal('tcp,!p2p-circuit')
122+
})
123+
67124
it('should handle non-json input', async () => {
68125
const cid = CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn')
69126

0 commit comments

Comments
 (0)