Skip to content

Commit 37adc28

Browse files
authored
feat: dht client (#3947)
* Enables `libp2p-kad-dht` in client mode * Updates types with new DHT events BREAKING CHANGE: The DHT API has been refactored to return async iterators of query events
1 parent be351fd commit 37adc28

File tree

8 files changed

+143
-69
lines changed

8 files changed

+143
-69
lines changed

src/dht/find-peer.js

+4-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { Multiaddr } from 'multiaddr'
21
import { configure } from '../lib/configure.js'
32
import { toUrlSearchParams } from '../lib/to-url-search-params.js'
4-
import { FinalPeer } from './response-types.js'
3+
import { mapEvent } from './map-event.js'
54

65
/**
76
* @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions
@@ -12,7 +11,7 @@ export const createFindPeer = configure(api => {
1211
/**
1312
* @type {DHTAPI["findPeer"]}
1413
*/
15-
async function findPeer (peerId, options = {}) {
14+
async function * findPeer (peerId, options = {}) {
1615
const res = await api.post('dht/findpeer', {
1716
signal: options.signal,
1817
searchParams: toUrlSearchParams({
@@ -22,17 +21,9 @@ export const createFindPeer = configure(api => {
2221
headers: options.headers
2322
})
2423

25-
for await (const data of res.ndjson()) {
26-
if (data.Type === FinalPeer && data.Responses) {
27-
const { ID, Addrs } = data.Responses[0]
28-
return {
29-
id: ID,
30-
addrs: (Addrs || []).map((/** @type {string} **/ a) => new Multiaddr(a))
31-
}
32-
}
24+
for await (const event of res.ndjson()) {
25+
yield mapEvent(event)
3326
}
34-
35-
throw new Error('not found')
3627
}
3728

3829
return findPeer

src/dht/find-provs.js

+3-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { Multiaddr } from 'multiaddr'
21
import { configure } from '../lib/configure.js'
32
import { toUrlSearchParams } from '../lib/to-url-search-params.js'
4-
import { Provider } from './response-types.js'
3+
import { mapEvent } from './map-event.js'
54

65
/**
76
* @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions
@@ -22,15 +21,8 @@ export const createFindProvs = configure(api => {
2221
headers: options.headers
2322
})
2423

25-
for await (const message of res.ndjson()) {
26-
if (message.Type === Provider && message.Responses) {
27-
for (const { ID, Addrs } of message.Responses) {
28-
yield {
29-
id: ID,
30-
addrs: (Addrs || []).map((/** @type {string} **/ a) => new Multiaddr(a))
31-
}
32-
}
33-
}
24+
for await (const event of res.ndjson()) {
25+
yield mapEvent(event)
3426
}
3527
}
3628

src/dht/get.js

+6-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { configure } from '../lib/configure.js'
22
import { toUrlSearchParams } from '../lib/to-url-search-params.js'
3-
import { Value } from './response-types.js'
4-
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
3+
import { mapEvent } from './map-event.js'
54
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
65

76
/**
@@ -13,23 +12,20 @@ export const createGet = configure(api => {
1312
/**
1413
* @type {DHTAPI["get"]}
1514
*/
16-
async function get (key, options = {}) {
15+
async function * get (key, options = {}) {
1716
const res = await api.post('dht/get', {
1817
signal: options.signal,
1918
searchParams: toUrlSearchParams({
20-
arg: key instanceof Uint8Array ? uint8ArrayToString(key) : key,
19+
// arg: base36.encode(key),
20+
arg: key instanceof Uint8Array ? uint8ArrayToString(key) : key.toString(),
2121
...options
2222
}),
2323
headers: options.headers
2424
})
2525

26-
for await (const message of res.ndjson()) {
27-
if (message.Type === Value) {
28-
return uint8ArrayFromString(message.Extra, 'base64pad')
29-
}
26+
for await (const event of res.ndjson()) {
27+
yield mapEvent(event)
3028
}
31-
32-
throw new Error('not found')
3329
}
3430

3531
return get

src/dht/map-event.js

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
2+
import {
3+
SendingQuery,
4+
PeerResponse,
5+
FinalPeer,
6+
QueryError,
7+
Provider,
8+
Value,
9+
AddingPeer,
10+
DialingPeer
11+
} from './response-types.js'
12+
import { Multiaddr } from 'multiaddr'
13+
14+
/**
15+
* @param {{Type: number, ID: string, Extra: string, Responses: {ID: string, Addrs: string[]}[]}} event
16+
* @returns {import('ipfs-core-types/src/dht').QueryEvent}
17+
*/
18+
export const mapEvent = (event) => {
19+
// console.info(JSON.stringify(event, null, 2))
20+
21+
if (event.Type === SendingQuery) {
22+
return {
23+
to: event.ID,
24+
name: 'SENDING_QUERY',
25+
type: event.Type
26+
}
27+
}
28+
29+
if (event.Type === PeerResponse) {
30+
return {
31+
from: event.ID,
32+
name: 'PEER_RESPONSE',
33+
type: event.Type,
34+
// TODO: how to infer this from the go-ipfs response
35+
messageType: 0,
36+
// TODO: how to infer this from the go-ipfs response
37+
messageName: 'PUT_VALUE',
38+
closer: (event.Responses || []).map(({ ID, Addrs }) => ({ id: ID, multiaddrs: Addrs.map(addr => new Multiaddr(addr)) })),
39+
providers: (event.Responses || []).map(({ ID, Addrs }) => ({ id: ID, multiaddrs: Addrs.map(addr => new Multiaddr(addr)) }))
40+
// TODO: how to infer this from the go-ipfs response
41+
// record: ???
42+
}
43+
}
44+
45+
if (event.Type === FinalPeer) {
46+
// dht.query ends with a FinalPeer event with no Responses
47+
let peer = {
48+
id: event.ID,
49+
/** @type {Multiaddr[]} */
50+
multiaddrs: []
51+
}
52+
53+
if (event.Responses && event.Responses.length) {
54+
// dht.findPeer has the result in the Responses field
55+
peer = {
56+
id: event.Responses[0].ID,
57+
multiaddrs: event.Responses[0].Addrs.map(addr => new Multiaddr(addr))
58+
}
59+
}
60+
61+
return {
62+
from: event.ID,
63+
name: 'FINAL_PEER',
64+
type: event.Type,
65+
peer
66+
}
67+
}
68+
69+
if (event.Type === QueryError) {
70+
return {
71+
from: event.ID,
72+
name: 'QUERY_ERROR',
73+
type: event.Type,
74+
error: new Error(event.Extra)
75+
}
76+
}
77+
78+
if (event.Type === Provider) {
79+
return {
80+
from: event.ID,
81+
name: 'PROVIDER',
82+
type: event.Type,
83+
providers: event.Responses.map(({ ID, Addrs }) => ({ id: ID, multiaddrs: Addrs.map(addr => new Multiaddr(addr)) }))
84+
}
85+
}
86+
87+
if (event.Type === Value) {
88+
return {
89+
from: event.ID,
90+
name: 'VALUE',
91+
type: event.Type,
92+
value: uint8ArrayFromString(event.Extra, 'base64pad')
93+
}
94+
}
95+
96+
if (event.Type === AddingPeer) {
97+
const peers = event.Responses.map(({ ID }) => ID)
98+
99+
if (!peers.length) {
100+
throw new Error('No peer found')
101+
}
102+
103+
return {
104+
name: 'ADDING_PEER',
105+
type: event.Type,
106+
peer: peers[0]
107+
}
108+
}
109+
110+
if (event.Type === DialingPeer) {
111+
return {
112+
name: 'DIALING_PEER',
113+
type: event.Type,
114+
peer: event.ID
115+
}
116+
}
117+
118+
throw new Error('Unknown DHT event type')
119+
}

src/dht/provide.js

+3-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { Multiaddr } from 'multiaddr'
2-
import { objectToCamel } from '../lib/object-to-camel.js'
31
import { configure } from '../lib/configure.js'
42
import { toUrlSearchParams } from '../lib/to-url-search-params.js'
3+
import { mapEvent } from './map-event.js'
54

65
/**
76
* @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions
@@ -26,17 +25,8 @@ export const createProvide = configure(api => {
2625
headers: options.headers
2726
})
2827

29-
for await (let message of res.ndjson()) {
30-
message = objectToCamel(message)
31-
if (message.responses) {
32-
message.responses = message.responses.map((/** @type {{ ID: string, Addrs: string[] }} */ { ID, Addrs }) => ({
33-
id: ID,
34-
addrs: (Addrs || []).map((/** @type {string} **/ a) => new Multiaddr(a))
35-
}))
36-
} else {
37-
message.responses = []
38-
}
39-
yield message
28+
for await (const event of res.ndjson()) {
29+
yield mapEvent(event)
4030
}
4131
}
4232

src/dht/put.js

+4-12
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { Multiaddr } from 'multiaddr'
2-
import { objectToCamel } from '../lib/object-to-camel.js'
31
import { configure } from '../lib/configure.js'
42
import { toUrlSearchParams } from '../lib/to-url-search-params.js'
53
import { multipartRequest } from 'ipfs-core-utils/multipart-request'
64
import { abortSignal } from '../lib/abort-signal.js'
75
import { AbortController } from 'native-abort-controller'
86
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
7+
import { mapEvent } from './map-event.js'
98

109
/**
1110
* @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions
@@ -24,23 +23,16 @@ export const createPut = configure(api => {
2423
const res = await api.post('dht/put', {
2524
signal,
2625
searchParams: toUrlSearchParams({
27-
arg: uint8ArrayToString(key),
26+
arg: key instanceof Uint8Array ? uint8ArrayToString(key) : key.toString(),
2827
...options
2928
}),
3029
...(
3130
await multipartRequest([value], controller, options.headers)
3231
)
3332
})
3433

35-
for await (let message of res.ndjson()) {
36-
message = objectToCamel(message)
37-
if (message.responses) {
38-
message.responses = message.responses.map((/** @type {{ ID: string, Addrs: string[] }} */ { ID, Addrs }) => ({
39-
id: ID,
40-
addrs: (Addrs || []).map(a => new Multiaddr(a))
41-
}))
42-
}
43-
yield message
34+
for await (const event of res.ndjson()) {
35+
yield mapEvent(event)
4436
}
4537
}
4638

src/dht/query.js

+3-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { Multiaddr } from 'multiaddr'
2-
import { objectToCamel } from '../lib/object-to-camel.js'
31
import { configure } from '../lib/configure.js'
42
import { toUrlSearchParams } from '../lib/to-url-search-params.js'
3+
import { mapEvent } from './map-event.js'
54

65
/**
76
* @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions
@@ -22,13 +21,8 @@ export const createQuery = configure(api => {
2221
headers: options.headers
2322
})
2423

25-
for await (let message of res.ndjson()) {
26-
message = objectToCamel(message)
27-
message.responses = (message.responses || []).map((/** @type {{ ID: string, Addrs: string[] }} */ { ID, Addrs }) => ({
28-
id: ID,
29-
addrs: (Addrs || []).map((/** @type {string} **/ a) => new Multiaddr(a))
30-
}))
31-
yield message
24+
for await (const event of res.ndjson()) {
25+
yield mapEvent(event)
3226
}
3327
}
3428

src/dht/response-types.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22
// Response types are defined here =
3-
// https =//github.com/libp2p/go-libp2p-core/blob/6e566d10f4a5447317a66d64c7459954b969bdab/routing/query.go#L15-L24
3+
// https://github.com/libp2p/go-libp2p-core/blob/6e566d10f4a5447317a66d64c7459954b969bdab/routing/query.go#L15-L24
44
export const SendingQuery = 0
55
export const PeerResponse = 1
66
export const FinalPeer = 2

0 commit comments

Comments
 (0)