@libp2p/daemon-client 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +4 -0
- package/README.md +84 -0
- package/dist/src/dht.d.ts +40 -0
- package/dist/src/dht.d.ts.map +1 -0
- package/dist/src/dht.js +230 -0
- package/dist/src/dht.js.map +1 -0
- package/dist/src/index.d.ts +21 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +195 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/pubsub.d.ts +21 -0
- package/dist/src/pubsub.d.ts.map +1 -0
- package/dist/src/pubsub.js +81 -0
- package/dist/src/pubsub.js.map +1 -0
- package/dist/src/stream-handler.d.ts +28 -0
- package/dist/src/stream-handler.d.ts.map +1 -0
- package/dist/src/stream-handler.js +47 -0
- package/dist/src/stream-handler.js.map +1 -0
- package/dist/src/util/index.d.ts +12 -0
- package/dist/src/util/index.d.ts.map +1 -0
- package/dist/src/util/index.js +24 -0
- package/dist/src/util/index.js.map +1 -0
- package/package.json +160 -0
- package/src/dht.ts +285 -0
- package/src/index.ts +263 -0
- package/src/pubsub.ts +106 -0
- package/src/stream-handler.ts +65 -0
- package/src/util/index.ts +27 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import errcode from 'err-code'
|
|
2
|
+
import { TCP } from '@libp2p/tcp'
|
|
3
|
+
import { IRequest, Request, Response } from '@libp2p/daemon-protocol'
|
|
4
|
+
import { StreamHandler } from './stream-handler.js'
|
|
5
|
+
import { Multiaddr } from '@multiformats/multiaddr'
|
|
6
|
+
import { DHT } from './dht.js'
|
|
7
|
+
import { Pubsub } from './pubsub.js'
|
|
8
|
+
import { isPeerId, PeerId } from '@libp2p/interfaces/peer-id'
|
|
9
|
+
import { passThroughUpgrader } from './util/index.js'
|
|
10
|
+
import type { ConnectionHandler, Listener } from '@libp2p/interfaces/transport'
|
|
11
|
+
import { peerIdFromBytes } from '@libp2p/peer-id'
|
|
12
|
+
import type { Duplex } from 'it-stream-types'
|
|
13
|
+
|
|
14
|
+
class Client implements DaemonClient {
|
|
15
|
+
private multiaddr: Multiaddr
|
|
16
|
+
public dht: DHT
|
|
17
|
+
public pubsub: Pubsub
|
|
18
|
+
private tcp: TCP
|
|
19
|
+
private listener?: Listener
|
|
20
|
+
|
|
21
|
+
constructor (addr: Multiaddr) {
|
|
22
|
+
this.multiaddr = addr
|
|
23
|
+
this.tcp = new TCP()
|
|
24
|
+
|
|
25
|
+
this.dht = new DHT(this)
|
|
26
|
+
this.pubsub = new Pubsub(this)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Connects to a daemon at the unix socket path the daemon
|
|
31
|
+
* was created with
|
|
32
|
+
*
|
|
33
|
+
* @async
|
|
34
|
+
* @returns {MultiaddrConnection}
|
|
35
|
+
*/
|
|
36
|
+
connectDaemon () {
|
|
37
|
+
return this.tcp.dial(this.multiaddr, {
|
|
38
|
+
upgrader: passThroughUpgrader
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Starts a server listening at `socketPath`. New connections
|
|
44
|
+
* will be sent to the `connectionHandler`.
|
|
45
|
+
*
|
|
46
|
+
* @param {Multiaddr} addr
|
|
47
|
+
* @param {function(Stream)} connectionHandler
|
|
48
|
+
* @returns {Promise}
|
|
49
|
+
*/
|
|
50
|
+
async start (addr: Multiaddr, connectionHandler: ConnectionHandler) {
|
|
51
|
+
if (this.listener) {
|
|
52
|
+
await this.close()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.listener = this.tcp.createListener({
|
|
56
|
+
handler: maConn => connectionHandler(maConn),
|
|
57
|
+
upgrader: passThroughUpgrader
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
await this.listener.listen(addr)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Sends the request to the daemon and returns a stream. This
|
|
65
|
+
* should only be used when sending daemon requests.
|
|
66
|
+
*/
|
|
67
|
+
async send (request: IRequest) {
|
|
68
|
+
const maConn = await this.connectDaemon()
|
|
69
|
+
|
|
70
|
+
const streamHandler = new StreamHandler({ stream: maConn })
|
|
71
|
+
streamHandler.write(Request.encode(request).finish())
|
|
72
|
+
return streamHandler
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Closes the socket
|
|
77
|
+
*/
|
|
78
|
+
async close () {
|
|
79
|
+
this.listener && await this.listener.close()
|
|
80
|
+
this.listener = undefined
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Connect requests a connection to a known peer on a given set of addresses
|
|
85
|
+
*
|
|
86
|
+
* @param {PeerId} peerId
|
|
87
|
+
* @param {Array.<multiaddr>} addrs
|
|
88
|
+
*/
|
|
89
|
+
async connect (peerId: PeerId, addrs: Multiaddr[]) {
|
|
90
|
+
if (!isPeerId(peerId)) {
|
|
91
|
+
throw errcode(new Error('invalid peer id received'), 'ERR_INVALID_PEER_ID')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!Array.isArray(addrs)) {
|
|
95
|
+
throw errcode(new Error('addrs received are not in an array'), 'ERR_INVALID_ADDRS_TYPE')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
addrs.forEach((addr) => {
|
|
99
|
+
if (!Multiaddr.isMultiaddr(addr)) {
|
|
100
|
+
throw errcode(new Error('received an address that is not a multiaddr'), 'ERR_NO_MULTIADDR_RECEIVED')
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const sh = await this.send({
|
|
105
|
+
type: Request.Type.CONNECT,
|
|
106
|
+
connect: {
|
|
107
|
+
peer: peerId.toBytes(),
|
|
108
|
+
addrs: addrs.map((a) => a.bytes)
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const message = await sh.read()
|
|
113
|
+
if (!message) {
|
|
114
|
+
throw errcode(new Error('unspecified'), 'ERR_CONNECT_FAILED')
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const response = Response.decode(message)
|
|
118
|
+
if (response.type !== Response.Type.OK) {
|
|
119
|
+
const errResponse = response.error ?? { msg: 'unspecified'}
|
|
120
|
+
throw errcode(new Error(errResponse.msg ?? 'unspecified'), 'ERR_CONNECT_FAILED')
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
await sh.close()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @typedef {Object} IdentifyResponse
|
|
128
|
+
* @property {PeerId} peerId
|
|
129
|
+
* @property {Array.<multiaddr>} addrs
|
|
130
|
+
*/
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Identify queries the daemon for its peer ID and listen addresses.
|
|
134
|
+
*/
|
|
135
|
+
async identify (): Promise<IdentifyResult> {
|
|
136
|
+
const sh = await this.send({
|
|
137
|
+
type: Request.Type.IDENTIFY
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
const message = await sh.read()
|
|
141
|
+
const response = Response.decode(message)
|
|
142
|
+
|
|
143
|
+
if (response.type !== Response.Type.OK) {
|
|
144
|
+
throw errcode(new Error(response.error?.msg ?? 'Identify failed'), 'ERR_IDENTIFY_FAILED')
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (response.identify == null || response.identify.addrs == null) {
|
|
148
|
+
throw errcode(new Error('Invalid response'), 'ERR_IDENTIFY_FAILED')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const peerId = peerIdFromBytes(response.identify?.id)
|
|
152
|
+
const addrs = response.identify.addrs.map((a) => new Multiaddr(a))
|
|
153
|
+
|
|
154
|
+
await sh.close()
|
|
155
|
+
|
|
156
|
+
return ({ peerId, addrs })
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get a list of IDs of peers the node is connected to
|
|
161
|
+
*/
|
|
162
|
+
async listPeers (): Promise<PeerId[]> {
|
|
163
|
+
const sh = await this.send({
|
|
164
|
+
type: Request.Type.LIST_PEERS
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
const message = await sh.read()
|
|
168
|
+
const response = Response.decode(message)
|
|
169
|
+
|
|
170
|
+
if (response.type !== Response.Type.OK) {
|
|
171
|
+
throw errcode(new Error(response.error?.msg ?? 'List peers failed'), 'ERR_LIST_PEERS_FAILED')
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
await sh.close()
|
|
175
|
+
|
|
176
|
+
return response.peers.map((peer) => peerIdFromBytes(peer.id))
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Initiate an outbound stream to a peer on one of a set of protocols.
|
|
181
|
+
*/
|
|
182
|
+
async openStream (peerId: PeerId, protocol: string): Promise<Duplex<Uint8Array>> {
|
|
183
|
+
if (!isPeerId(peerId)) {
|
|
184
|
+
throw errcode(new Error('invalid peer id received'), 'ERR_INVALID_PEER_ID')
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (typeof protocol !== 'string') {
|
|
188
|
+
throw errcode(new Error('invalid protocol received'), 'ERR_INVALID_PROTOCOL')
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const sh = await this.send({
|
|
192
|
+
type: Request.Type.STREAM_OPEN,
|
|
193
|
+
streamOpen: {
|
|
194
|
+
peer: peerId.toBytes(),
|
|
195
|
+
proto: [protocol]
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
const message = await sh.read()
|
|
200
|
+
const response = Response.decode(message)
|
|
201
|
+
|
|
202
|
+
if (response.type !== Response.Type.OK) {
|
|
203
|
+
await sh.close()
|
|
204
|
+
throw errcode(new Error(response.error?.msg ?? 'Open stream failed'), 'ERR_OPEN_STREAM_FAILED')
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return sh.rest()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Register a handler for inbound streams on a given protocol
|
|
212
|
+
*/
|
|
213
|
+
async registerStreamHandler (addr: Multiaddr, protocol: string) {
|
|
214
|
+
if (!Multiaddr.isMultiaddr(addr)) {
|
|
215
|
+
throw errcode(new Error('invalid multiaddr received'), 'ERR_INVALID_MULTIADDR')
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (typeof protocol !== 'string') {
|
|
219
|
+
throw errcode(new Error('invalid protocol received'), 'ERR_INVALID_PROTOCOL')
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const sh = await this.send({
|
|
223
|
+
type: Request.Type.STREAM_HANDLER,
|
|
224
|
+
streamOpen: null,
|
|
225
|
+
streamHandler: {
|
|
226
|
+
addr: addr.bytes,
|
|
227
|
+
proto: [protocol]
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
const message = await sh.read()
|
|
232
|
+
const response = Response.decode(message)
|
|
233
|
+
|
|
234
|
+
await sh.close()
|
|
235
|
+
|
|
236
|
+
if (response.type !== Response.Type.OK) {
|
|
237
|
+
throw errcode(new Error(response.error?.msg ?? 'Register stream handler failed'), 'ERR_REGISTER_STREAM_HANDLER_FAILED')
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export interface IdentifyResult {
|
|
243
|
+
peerId: PeerId
|
|
244
|
+
addrs: Multiaddr[]
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export interface DHTClient {
|
|
248
|
+
put: (key: Uint8Array, value: Uint8Array) => Promise<void>
|
|
249
|
+
get: (key: Uint8Array) => Promise<Uint8Array>
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export interface DaemonClient {
|
|
253
|
+
identify: () => Promise<IdentifyResult>
|
|
254
|
+
listPeers: () => Promise<PeerId[]>
|
|
255
|
+
connect: (peerId: PeerId, addrs: Multiaddr[]) => Promise<void>
|
|
256
|
+
dht: DHTClient
|
|
257
|
+
|
|
258
|
+
send: (request: IRequest) => Promise<StreamHandler>
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function createClient (multiaddr: Multiaddr): DaemonClient {
|
|
262
|
+
return new Client(multiaddr)
|
|
263
|
+
}
|
package/src/pubsub.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import errcode from 'err-code'
|
|
2
|
+
import {
|
|
3
|
+
Request,
|
|
4
|
+
Response,
|
|
5
|
+
PSRequest,
|
|
6
|
+
PSMessage
|
|
7
|
+
} from '@libp2p/daemon-protocol'
|
|
8
|
+
import type { DaemonClient } from './index.js'
|
|
9
|
+
|
|
10
|
+
export class Pubsub {
|
|
11
|
+
private client: DaemonClient
|
|
12
|
+
|
|
13
|
+
constructor (client: DaemonClient) {
|
|
14
|
+
this.client = client
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get a list of topics the node is subscribed to.
|
|
19
|
+
*
|
|
20
|
+
* @returns {Array<string>} topics
|
|
21
|
+
*/
|
|
22
|
+
async getTopics (): Promise<string[]> {
|
|
23
|
+
const sh = await this.client.send({
|
|
24
|
+
type: Request.Type.PUBSUB,
|
|
25
|
+
pubsub: {
|
|
26
|
+
type: PSRequest.Type.GET_TOPICS
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const message = await sh.read()
|
|
31
|
+
const response = Response.decode(message)
|
|
32
|
+
|
|
33
|
+
await sh.close()
|
|
34
|
+
|
|
35
|
+
if (response.type !== Response.Type.OK) {
|
|
36
|
+
throw errcode(new Error(response.error?.msg ?? 'Pubsub get topics failed'), 'ERR_PUBSUB_GET_TOPICS_FAILED')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (response.pubsub == null || response.pubsub.topics == null) {
|
|
40
|
+
throw errcode(new Error('Invalid response'), 'ERR_PUBSUB_GET_TOPICS_FAILED')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return response.pubsub.topics
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Publish data under a topic
|
|
48
|
+
*/
|
|
49
|
+
async publish (topic: string, data: Uint8Array) {
|
|
50
|
+
if (typeof topic !== 'string') {
|
|
51
|
+
throw errcode(new Error('invalid topic received'), 'ERR_INVALID_TOPIC')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!(data instanceof Uint8Array)) {
|
|
55
|
+
throw errcode(new Error('data received is not a Uint8Array'), 'ERR_INVALID_DATA')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const sh = await this.client.send({
|
|
59
|
+
type: Request.Type.PUBSUB,
|
|
60
|
+
pubsub: {
|
|
61
|
+
type: PSRequest.Type.PUBLISH,
|
|
62
|
+
topic,
|
|
63
|
+
data
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const message = await sh.read()
|
|
68
|
+
const response = Response.decode(message)
|
|
69
|
+
|
|
70
|
+
await sh.close()
|
|
71
|
+
|
|
72
|
+
if (response.type !== Response.Type.OK) {
|
|
73
|
+
throw errcode(new Error(response.error?.msg ?? 'Pubsub publish failed'), 'ERR_PUBSUB_PUBLISH_FAILED')
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Request to subscribe a certain topic
|
|
79
|
+
*/
|
|
80
|
+
async * subscribe (topic: string) {
|
|
81
|
+
if (typeof topic !== 'string') {
|
|
82
|
+
throw errcode(new Error('invalid topic received'), 'ERR_INVALID_TOPIC')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const sh = await this.client.send({
|
|
86
|
+
type: Request.Type.PUBSUB,
|
|
87
|
+
pubsub: {
|
|
88
|
+
type: PSRequest.Type.SUBSCRIBE,
|
|
89
|
+
topic
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
let message = await sh.read()
|
|
94
|
+
let response = Response.decode(message)
|
|
95
|
+
|
|
96
|
+
if (response.type !== Response.Type.OK) {
|
|
97
|
+
throw errcode(new Error(response.error?.msg ?? 'Pubsub publish failed'), 'ERR_PUBSUB_PUBLISH_FAILED')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// stream messages
|
|
101
|
+
while (true) {
|
|
102
|
+
message = await sh.read()
|
|
103
|
+
yield PSMessage.decode(message)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as lp from 'it-length-prefixed'
|
|
2
|
+
import { handshake } from 'it-handshake'
|
|
3
|
+
import { logger } from '@libp2p/logger'
|
|
4
|
+
import type { Duplex, Source } from 'it-stream-types'
|
|
5
|
+
import type { Handshake } from 'it-handshake'
|
|
6
|
+
|
|
7
|
+
const log = logger('libp2p:daemon-client:stream-handler')
|
|
8
|
+
|
|
9
|
+
export interface StreamHandlerOptions {
|
|
10
|
+
stream: Duplex<Uint8Array>
|
|
11
|
+
maxLength?: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class StreamHandler {
|
|
15
|
+
private stream: Duplex<Uint8Array>
|
|
16
|
+
private shake: Handshake
|
|
17
|
+
private decoder: Source<Uint8Array>
|
|
18
|
+
/**
|
|
19
|
+
* Create a stream handler for connection
|
|
20
|
+
*/
|
|
21
|
+
constructor (opts: StreamHandlerOptions) {
|
|
22
|
+
const { stream, maxLength } = opts
|
|
23
|
+
|
|
24
|
+
this.stream = stream
|
|
25
|
+
this.shake = handshake(this.stream)
|
|
26
|
+
this.decoder = lp.decode.fromReader(this.shake.reader, { maxDataLength: maxLength ?? 4096 })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Read and decode message
|
|
31
|
+
*/
|
|
32
|
+
async read () {
|
|
33
|
+
// @ts-expect-error decoder is really a generator
|
|
34
|
+
const msg = await this.decoder.next()
|
|
35
|
+
if (msg.value) {
|
|
36
|
+
return msg.value.slice()
|
|
37
|
+
}
|
|
38
|
+
log('read received no value, closing stream')
|
|
39
|
+
// End the stream, we didn't get data
|
|
40
|
+
this.close()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
write (msg: Uint8Array) {
|
|
44
|
+
log('write message')
|
|
45
|
+
this.shake.write(
|
|
46
|
+
lp.encode.single(msg).slice()
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Return the handshake rest stream and invalidate handler
|
|
52
|
+
*/
|
|
53
|
+
rest () {
|
|
54
|
+
this.shake.rest()
|
|
55
|
+
return this.shake.stream
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Close the stream
|
|
60
|
+
*/
|
|
61
|
+
close () {
|
|
62
|
+
log('closing the stream')
|
|
63
|
+
this.rest().sink([])
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Upgrader } from '@libp2p/interfaces/transport'
|
|
2
|
+
import type { Multiaddr } from '@multiformats/multiaddr'
|
|
3
|
+
import { resolve } from 'path'
|
|
4
|
+
|
|
5
|
+
export const passThroughUpgrader: Upgrader = {
|
|
6
|
+
// @ts-expect-error
|
|
7
|
+
upgradeInbound: maConn => maConn,
|
|
8
|
+
// @ts-expect-error
|
|
9
|
+
upgradeOutbound: maConn => maConn
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Converts the multiaddr to a nodejs NET compliant option
|
|
14
|
+
* for .connect or .listen
|
|
15
|
+
*
|
|
16
|
+
* @param {Multiaddr} addr
|
|
17
|
+
* @returns {string|object} A nodejs NET compliant option
|
|
18
|
+
*/
|
|
19
|
+
export function multiaddrToNetConfig (addr: Multiaddr) {
|
|
20
|
+
const listenPath = addr.getPath()
|
|
21
|
+
// unix socket listening
|
|
22
|
+
if (listenPath) {
|
|
23
|
+
return resolve(listenPath)
|
|
24
|
+
}
|
|
25
|
+
// tcp listening
|
|
26
|
+
return addr.nodeAddress()
|
|
27
|
+
}
|