@libp2p/daemon-server 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 +43 -0
- package/dist/src/client.d.ts +26 -0
- package/dist/src/client.d.ts.map +1 -0
- package/dist/src/client.js +43 -0
- package/dist/src/client.js.map +1 -0
- package/dist/src/dht.d.ts +18 -0
- package/dist/src/dht.d.ts.map +1 -0
- package/dist/src/dht.js +127 -0
- package/dist/src/dht.js.map +1 -0
- package/dist/src/index.d.ts +103 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +395 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/pubsub.d.ts +12 -0
- package/dist/src/pubsub.d.ts.map +1 -0
- package/dist/src/pubsub.js +39 -0
- package/dist/src/pubsub.js.map +1 -0
- package/dist/src/responses.d.ts +10 -0
- package/dist/src/responses.d.ts.map +1 -0
- package/dist/src/responses.js +22 -0
- package/dist/src/responses.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 +13 -0
- package/dist/src/util/index.d.ts.map +1 -0
- package/dist/src/util/index.js +26 -0
- package/dist/src/util/index.js.map +1 -0
- package/package.json +165 -0
- package/src/client.ts +56 -0
- package/src/dht.ts +153 -0
- package/src/index.ts +513 -0
- package/src/pubsub.ts +57 -0
- package/src/responses.ts +23 -0
- package/src/stream-handler.ts +65 -0
- package/src/util/index.ts +30 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
/* eslint max-depth: ["error", 6] */
|
|
2
|
+
|
|
3
|
+
import { TCP } from '@libp2p/tcp'
|
|
4
|
+
import { Multiaddr } from '@multiformats/multiaddr'
|
|
5
|
+
import { CID } from 'multiformats/cid'
|
|
6
|
+
import * as lp from 'it-length-prefixed'
|
|
7
|
+
import { pipe } from 'it-pipe'
|
|
8
|
+
import { StreamHandler } from './stream-handler.js'
|
|
9
|
+
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
10
|
+
import { passThroughUpgrader } from './util/index.js'
|
|
11
|
+
import {
|
|
12
|
+
Request,
|
|
13
|
+
DHTRequest,
|
|
14
|
+
PeerstoreRequest,
|
|
15
|
+
PSRequest,
|
|
16
|
+
StreamInfo,
|
|
17
|
+
IRequest,
|
|
18
|
+
IStreamInfo,
|
|
19
|
+
IPSRequest,
|
|
20
|
+
IDHTRequest,
|
|
21
|
+
IPeerstoreRequest
|
|
22
|
+
} from '@libp2p/daemon-protocol'
|
|
23
|
+
import type { Listener } from '@libp2p/interfaces/transport'
|
|
24
|
+
import type { Connection, Stream } from '@libp2p/interfaces/connection'
|
|
25
|
+
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
|
26
|
+
import type { AbortOptions } from '@libp2p/interfaces'
|
|
27
|
+
import type { StreamHandler as StreamCallback } from '@libp2p/interfaces/registrar'
|
|
28
|
+
import type { DualDHT } from '@libp2p/interfaces/dht'
|
|
29
|
+
import type { PubSub } from '@libp2p/interfaces/pubsub'
|
|
30
|
+
import type { PeerStore } from '@libp2p/interfaces/peer-store'
|
|
31
|
+
import { ErrorResponse, OkResponse } from './responses.js'
|
|
32
|
+
import { DHTOperations } from './dht.js'
|
|
33
|
+
import { peerIdFromBytes, peerIdFromString } from '@libp2p/peer-id'
|
|
34
|
+
import { PubSubOperations } from './pubsub.js'
|
|
35
|
+
import { logger } from '@libp2p/logger'
|
|
36
|
+
|
|
37
|
+
const LIMIT = 1 << 22 // 4MB
|
|
38
|
+
const log = logger('libp2p:daemon')
|
|
39
|
+
|
|
40
|
+
export interface OpenStream {
|
|
41
|
+
streamInfo: IStreamInfo,
|
|
42
|
+
connection: Stream
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface Libp2p {
|
|
46
|
+
peerId: PeerId
|
|
47
|
+
peerStore: PeerStore
|
|
48
|
+
pubsub?: PubSub
|
|
49
|
+
dht?: DualDHT
|
|
50
|
+
|
|
51
|
+
getConnections: (peerId?: PeerId) => Connection[]
|
|
52
|
+
getPeers: () => PeerId[]
|
|
53
|
+
dial: (peer: PeerId | Multiaddr, options?: AbortOptions) => Promise<Connection>
|
|
54
|
+
handle: (protocol: string | string[], handler: StreamCallback) => Promise<void>
|
|
55
|
+
start: () => void | Promise<void>
|
|
56
|
+
stop: () => void | Promise<void>
|
|
57
|
+
getMultiaddrs: () => Multiaddr[]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface DaemonInit {
|
|
61
|
+
multiaddr: Multiaddr,
|
|
62
|
+
libp2pNode: any
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface Libp2pServer {
|
|
66
|
+
start: () => Promise<void>
|
|
67
|
+
stop: () => Promise<void>
|
|
68
|
+
getMultiaddrs: () => Multiaddr[]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export class Server implements Libp2pServer {
|
|
72
|
+
private multiaddr: Multiaddr
|
|
73
|
+
private libp2p: Libp2p
|
|
74
|
+
private tcp: TCP
|
|
75
|
+
private listener: Listener
|
|
76
|
+
private streamHandlers: Record<string, StreamHandler>
|
|
77
|
+
private dhtOperations?: DHTOperations
|
|
78
|
+
private pubsubOperations?: PubSubOperations
|
|
79
|
+
|
|
80
|
+
constructor (init: DaemonInit) {
|
|
81
|
+
const { multiaddr, libp2pNode } = init
|
|
82
|
+
|
|
83
|
+
this.multiaddr = multiaddr
|
|
84
|
+
this.libp2p = libp2pNode
|
|
85
|
+
this.tcp = new TCP()
|
|
86
|
+
this.listener = this.tcp.createListener({
|
|
87
|
+
handler: this.handleConnection.bind(this),
|
|
88
|
+
upgrader: passThroughUpgrader
|
|
89
|
+
})
|
|
90
|
+
this.streamHandlers = {}
|
|
91
|
+
this._onExit = this._onExit.bind(this)
|
|
92
|
+
|
|
93
|
+
if (libp2pNode.dht != null) {
|
|
94
|
+
this.dhtOperations = new DHTOperations({ dht: libp2pNode.dht })
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (libp2pNode.pubsub != null) {
|
|
98
|
+
this.pubsubOperations = new PubSubOperations({ pubsub: libp2pNode.pubsub })
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Connects the daemons libp2p node to the peer provided
|
|
104
|
+
*/
|
|
105
|
+
async connect (request: IRequest): Promise<Connection> {
|
|
106
|
+
if (request.connect == null || request.connect.addrs == null) {
|
|
107
|
+
throw new Error('Invalid request')
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const peer = request.connect.peer
|
|
111
|
+
const addrs = request.connect.addrs.map((a) => new Multiaddr(a))
|
|
112
|
+
const peerId = peerIdFromBytes(peer)
|
|
113
|
+
|
|
114
|
+
await this.libp2p.peerStore.addressBook.set(peerId, addrs)
|
|
115
|
+
return this.libp2p.dial(peerId)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Opens a stream on one of the given protocols to the given peer
|
|
120
|
+
*/
|
|
121
|
+
async openStream (request: IRequest): Promise<OpenStream> {
|
|
122
|
+
if (request.streamOpen == null || request.streamOpen.proto == null) {
|
|
123
|
+
throw new Error('Invalid request')
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const { peer, proto } = request.streamOpen
|
|
127
|
+
|
|
128
|
+
const peerId = peerIdFromString(uint8ArrayToString(peer, 'base58btc'))
|
|
129
|
+
|
|
130
|
+
const connection = await this.libp2p.dial(peerId)
|
|
131
|
+
const { stream, protocol } = await connection.newStream(proto)
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
streamInfo: {
|
|
135
|
+
peer: peerId.toBytes(),
|
|
136
|
+
addr: connection.remoteAddr.bytes,
|
|
137
|
+
proto: protocol
|
|
138
|
+
},
|
|
139
|
+
connection: stream
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Sends inbound requests for the given protocol
|
|
145
|
+
* to the unix socket path provided. If an existing handler
|
|
146
|
+
* is registered at the path, it will be overridden.
|
|
147
|
+
*
|
|
148
|
+
* @param {StreamHandlerRequest} request
|
|
149
|
+
* @returns {Promise<void>}
|
|
150
|
+
*/
|
|
151
|
+
async registerStreamHandler (request: IRequest): Promise<void> {
|
|
152
|
+
if (request.streamHandler == null || request.streamHandler.proto == null) {
|
|
153
|
+
throw new Error('Invalid request')
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const protocols = request.streamHandler.proto
|
|
157
|
+
const addr = new Multiaddr(request.streamHandler.addr)
|
|
158
|
+
const addrString = addr.toString()
|
|
159
|
+
|
|
160
|
+
// If we have a handler, end it
|
|
161
|
+
if (this.streamHandlers[addrString]) {
|
|
162
|
+
this.streamHandlers[addrString].close()
|
|
163
|
+
delete this.streamHandlers[addrString]
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
protocols.forEach((proto) => {
|
|
167
|
+
// Connect the client socket with the libp2p connection
|
|
168
|
+
this.libp2p.handle(proto, ({ connection, stream, protocol }) => {
|
|
169
|
+
const message = StreamInfo.encode({
|
|
170
|
+
peer: connection.remotePeer.toBytes(),
|
|
171
|
+
addr: connection.remoteAddr.bytes,
|
|
172
|
+
proto: protocol
|
|
173
|
+
}).finish()
|
|
174
|
+
const encodedMessage = lp.encode.single(message)
|
|
175
|
+
|
|
176
|
+
// Tell the client about the new connection
|
|
177
|
+
// And then begin piping the client and peer connection
|
|
178
|
+
|
|
179
|
+
pipe(
|
|
180
|
+
[encodedMessage, stream.source],
|
|
181
|
+
clientConnection,
|
|
182
|
+
stream.sink
|
|
183
|
+
)
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
const clientConnection = await this.tcp.dial(addr, {
|
|
188
|
+
upgrader: passThroughUpgrader
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Listens for process exit to handle cleanup
|
|
194
|
+
*
|
|
195
|
+
* @private
|
|
196
|
+
* @returns {void}
|
|
197
|
+
*/
|
|
198
|
+
_listen () {
|
|
199
|
+
// listen for graceful termination
|
|
200
|
+
process.on('SIGTERM', this._onExit)
|
|
201
|
+
process.on('SIGINT', this._onExit)
|
|
202
|
+
process.on('SIGHUP', this._onExit)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
_onExit () {
|
|
206
|
+
this.stop({ exit: true })
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Starts the daemon
|
|
211
|
+
*/
|
|
212
|
+
async start () {
|
|
213
|
+
this._listen()
|
|
214
|
+
await this.libp2p.start()
|
|
215
|
+
await this.listener.listen(this.multiaddr)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
getMultiaddrs (): Multiaddr[] {
|
|
219
|
+
return this.listener.getAddrs()
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Stops the daemon
|
|
224
|
+
*
|
|
225
|
+
* @param {object} options
|
|
226
|
+
* @param {boolean} options.exit - If the daemon process should exit
|
|
227
|
+
* @returns {Promise<void>}
|
|
228
|
+
*/
|
|
229
|
+
async stop (options = { exit: false }) {
|
|
230
|
+
await this.libp2p.stop()
|
|
231
|
+
await this.listener.close()
|
|
232
|
+
if (options.exit) {
|
|
233
|
+
log('server closed, exiting')
|
|
234
|
+
}
|
|
235
|
+
process.removeListener('SIGTERM', this._onExit)
|
|
236
|
+
process.removeListener('SIGINT', this._onExit)
|
|
237
|
+
process.removeListener('SIGHUP', this._onExit)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async * handlePeerStoreRequest (request: IPeerstoreRequest) {
|
|
241
|
+
try {
|
|
242
|
+
switch (request.type) {
|
|
243
|
+
case PeerstoreRequest.Type.GET_PROTOCOLS:
|
|
244
|
+
if (request.id == null) {
|
|
245
|
+
throw new Error('Invalid request')
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const peerId = peerIdFromBytes(request.id)
|
|
249
|
+
const peer = await this.libp2p.peerStore.get(peerId)
|
|
250
|
+
const protos = peer.protocols
|
|
251
|
+
yield OkResponse({ peerStore: { protos } })
|
|
252
|
+
return
|
|
253
|
+
case PeerstoreRequest.Type.GET_PEER_INFO:
|
|
254
|
+
throw new Error('ERR_NOT_IMPLEMENTED')
|
|
255
|
+
default:
|
|
256
|
+
throw new Error('ERR_INVALID_REQUEST_TYPE')
|
|
257
|
+
}
|
|
258
|
+
} catch (err: any) {
|
|
259
|
+
yield ErrorResponse(err)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Parses and responds to PSRequests
|
|
265
|
+
*/
|
|
266
|
+
async * handlePubsubRequest (request: IPSRequest) {
|
|
267
|
+
try {
|
|
268
|
+
if (this.libp2p.pubsub == null || !this.pubsubOperations) {
|
|
269
|
+
throw new Error('PubSub not configured')
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
switch (request.type) {
|
|
273
|
+
case PSRequest.Type.GET_TOPICS:
|
|
274
|
+
yield * this.pubsubOperations.getTopics()
|
|
275
|
+
return
|
|
276
|
+
case PSRequest.Type.SUBSCRIBE:
|
|
277
|
+
if (request.topic == null) {
|
|
278
|
+
throw new Error('Invalid request')
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
yield * this.pubsubOperations.subscribe(request.topic)
|
|
282
|
+
return
|
|
283
|
+
case PSRequest.Type.PUBLISH:
|
|
284
|
+
if (request.topic == null || request.data == null) {
|
|
285
|
+
throw new Error('Invalid request')
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
yield * this.pubsubOperations.publish(request.topic, request.data)
|
|
289
|
+
return
|
|
290
|
+
default:
|
|
291
|
+
throw new Error('ERR_INVALID_REQUEST_TYPE')
|
|
292
|
+
}
|
|
293
|
+
} catch (err: any) {
|
|
294
|
+
yield ErrorResponse(err)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Parses and responds to DHTRequests
|
|
300
|
+
*/
|
|
301
|
+
async * handleDHTRequest (request: IDHTRequest) {
|
|
302
|
+
try {
|
|
303
|
+
if (this.libp2p.dht == null || !this.dhtOperations) {
|
|
304
|
+
throw new Error('DHT not configured')
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
switch (request.type) {
|
|
308
|
+
case DHTRequest.Type.FIND_PEER:
|
|
309
|
+
if (request.peer == null) {
|
|
310
|
+
throw new Error('Invalid request')
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
yield * this.dhtOperations.findPeer(peerIdFromBytes(request.peer))
|
|
314
|
+
return
|
|
315
|
+
case DHTRequest.Type.FIND_PROVIDERS:
|
|
316
|
+
if (request.cid == null) {
|
|
317
|
+
throw new Error('Invalid request')
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
yield * this.dhtOperations.findProviders(CID.decode(request.cid), request.count ?? 20)
|
|
321
|
+
return
|
|
322
|
+
case DHTRequest.Type.PROVIDE:
|
|
323
|
+
if (request.cid == null) {
|
|
324
|
+
throw new Error('Invalid request')
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
yield * this.dhtOperations.provide(CID.decode(request.cid))
|
|
328
|
+
return
|
|
329
|
+
case DHTRequest.Type.GET_CLOSEST_PEERS:
|
|
330
|
+
if (request.key == null) {
|
|
331
|
+
throw new Error('Invalid request')
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
yield * this.dhtOperations.getClosestPeers(request.key)
|
|
335
|
+
return
|
|
336
|
+
case DHTRequest.Type.GET_PUBLIC_KEY:
|
|
337
|
+
if (request.peer == null) {
|
|
338
|
+
throw new Error('Invalid request')
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
yield * this.dhtOperations.getPublicKey(peerIdFromBytes(request.peer))
|
|
342
|
+
return
|
|
343
|
+
case DHTRequest.Type.GET_VALUE:
|
|
344
|
+
if (request.key == null) {
|
|
345
|
+
throw new Error('Invalid request')
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
yield * this.dhtOperations.getValue(request.key)
|
|
349
|
+
return
|
|
350
|
+
case DHTRequest.Type.PUT_VALUE:
|
|
351
|
+
if (request.key == null || request.value == null) {
|
|
352
|
+
throw new Error('Invalid request')
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
yield * this.dhtOperations.putValue(request.key, request.value)
|
|
356
|
+
return
|
|
357
|
+
default:
|
|
358
|
+
throw new Error('ERR_INVALID_REQUEST_TYPE')
|
|
359
|
+
}
|
|
360
|
+
} catch (err: any) {
|
|
361
|
+
yield ErrorResponse(err)
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Handles requests for the given connection
|
|
367
|
+
*/
|
|
368
|
+
async handleConnection (connection: Connection) {
|
|
369
|
+
const daemon = this
|
|
370
|
+
// @ts-expect-error connection may actually be a maconn?
|
|
371
|
+
const streamHandler = new StreamHandler({ stream: connection, maxLength: LIMIT })
|
|
372
|
+
|
|
373
|
+
await pipe(
|
|
374
|
+
streamHandler.decoder,
|
|
375
|
+
source => (async function * () {
|
|
376
|
+
let request: Request
|
|
377
|
+
|
|
378
|
+
for await (let buf of source) {
|
|
379
|
+
try {
|
|
380
|
+
request = Request.decode(buf)
|
|
381
|
+
} catch (err) {
|
|
382
|
+
yield ErrorResponse(new Error('ERR_INVALID_MESSAGE'))
|
|
383
|
+
continue
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
switch (request.type) {
|
|
387
|
+
// Connect to another peer
|
|
388
|
+
case Request.Type.CONNECT: {
|
|
389
|
+
try {
|
|
390
|
+
await daemon.connect(request)
|
|
391
|
+
} catch (err: any) {
|
|
392
|
+
yield ErrorResponse(err)
|
|
393
|
+
break
|
|
394
|
+
}
|
|
395
|
+
yield OkResponse()
|
|
396
|
+
break
|
|
397
|
+
}
|
|
398
|
+
// Get the daemon peer id and addresses
|
|
399
|
+
case Request.Type.IDENTIFY: {
|
|
400
|
+
yield OkResponse({
|
|
401
|
+
identify: {
|
|
402
|
+
id: daemon.libp2p.peerId.toBytes(),
|
|
403
|
+
addrs: daemon.libp2p.getMultiaddrs().map(m => m.bytes)
|
|
404
|
+
}
|
|
405
|
+
})
|
|
406
|
+
break
|
|
407
|
+
}
|
|
408
|
+
// Get a list of our current peers
|
|
409
|
+
case Request.Type.LIST_PEERS: {
|
|
410
|
+
const peers = []
|
|
411
|
+
|
|
412
|
+
for (const peerId of daemon.libp2p.getPeers()) {
|
|
413
|
+
const conn = daemon.libp2p.getConnections(peerId)[0]
|
|
414
|
+
|
|
415
|
+
peers.push({
|
|
416
|
+
id: peerId.toBytes(),
|
|
417
|
+
addrs: [conn.remoteAddr.bytes]
|
|
418
|
+
})
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
yield OkResponse({ peers })
|
|
422
|
+
break
|
|
423
|
+
}
|
|
424
|
+
case Request.Type.STREAM_OPEN: {
|
|
425
|
+
let response
|
|
426
|
+
try {
|
|
427
|
+
response = await daemon.openStream(request)
|
|
428
|
+
} catch (err: any) {
|
|
429
|
+
yield ErrorResponse(err.message)
|
|
430
|
+
break
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// write the response
|
|
434
|
+
yield OkResponse({
|
|
435
|
+
streamInfo: response.streamInfo
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
const stream = streamHandler.rest()
|
|
439
|
+
// then pipe the connection to the client
|
|
440
|
+
await pipe(
|
|
441
|
+
stream,
|
|
442
|
+
response.connection,
|
|
443
|
+
stream
|
|
444
|
+
)
|
|
445
|
+
// Exit the iterator, no more requests can come through
|
|
446
|
+
return
|
|
447
|
+
}
|
|
448
|
+
case Request.Type.STREAM_HANDLER: {
|
|
449
|
+
try {
|
|
450
|
+
await daemon.registerStreamHandler(request)
|
|
451
|
+
} catch (err: any) {
|
|
452
|
+
yield ErrorResponse(err)
|
|
453
|
+
break
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// write the response
|
|
457
|
+
yield OkResponse()
|
|
458
|
+
break
|
|
459
|
+
}
|
|
460
|
+
case Request.Type.PEERSTORE: {
|
|
461
|
+
if (request.peerStore == null) {
|
|
462
|
+
yield ErrorResponse(new Error('ERR_INVALID_REQUEST'))
|
|
463
|
+
break
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
yield * daemon.handlePeerStoreRequest(request.peerStore)
|
|
467
|
+
break
|
|
468
|
+
}
|
|
469
|
+
case Request.Type.PUBSUB: {
|
|
470
|
+
if (request.pubsub == null) {
|
|
471
|
+
yield ErrorResponse(new Error('ERR_INVALID_REQUEST'))
|
|
472
|
+
break
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
yield * daemon.handlePubsubRequest(request.pubsub)
|
|
476
|
+
break
|
|
477
|
+
}
|
|
478
|
+
case Request.Type.DHT: {
|
|
479
|
+
if (request.dht == null) {
|
|
480
|
+
yield ErrorResponse(new Error('ERR_INVALID_REQUEST'))
|
|
481
|
+
break
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
yield * daemon.handleDHTRequest(request.dht)
|
|
485
|
+
break
|
|
486
|
+
}
|
|
487
|
+
// Not yet supported or doesn't exist
|
|
488
|
+
default:
|
|
489
|
+
yield ErrorResponse(new Error('ERR_INVALID_REQUEST_TYPE'))
|
|
490
|
+
break
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
})(),
|
|
494
|
+
async function (source) {
|
|
495
|
+
for await (const result of source) {
|
|
496
|
+
streamHandler.write(result)
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
)
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Creates a daemon from the provided Daemon Options
|
|
505
|
+
*/
|
|
506
|
+
export const createServer = async (multiaddr: Multiaddr, libp2pNode: Libp2p): Promise<Libp2pServer> => {
|
|
507
|
+
const daemon = new Server({
|
|
508
|
+
multiaddr,
|
|
509
|
+
libp2pNode
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
return daemon
|
|
513
|
+
}
|
package/src/pubsub.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/* eslint max-depth: ["error", 6] */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
PSMessage
|
|
5
|
+
} from '@libp2p/daemon-protocol'
|
|
6
|
+
import { OkResponse } from './responses.js'
|
|
7
|
+
import type { PubSub } from '@libp2p/interfaces/pubsub'
|
|
8
|
+
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
9
|
+
import { pushable } from 'it-pushable'
|
|
10
|
+
|
|
11
|
+
export interface PubSubOperationsInit {
|
|
12
|
+
pubsub: PubSub
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class PubSubOperations {
|
|
16
|
+
private pubsub: PubSub
|
|
17
|
+
|
|
18
|
+
constructor (init: PubSubOperationsInit) {
|
|
19
|
+
const { pubsub } = init
|
|
20
|
+
|
|
21
|
+
this.pubsub = pubsub
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async * getTopics () {
|
|
25
|
+
yield OkResponse({
|
|
26
|
+
pubsub: {
|
|
27
|
+
topics: this.pubsub.getTopics()
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async * subscribe (topic: string) {
|
|
33
|
+
const onMessage = pushable<Uint8Array>()
|
|
34
|
+
|
|
35
|
+
await this.pubsub.addEventListener(topic, (evt) => {
|
|
36
|
+
const msg = evt.detail
|
|
37
|
+
|
|
38
|
+
onMessage.push(PSMessage.encode({
|
|
39
|
+
from: msg.from.toBytes(),
|
|
40
|
+
data: msg.data,
|
|
41
|
+
seqno: msg.sequenceNumber == null ? undefined : uint8ArrayFromString(msg.sequenceNumber.toString(16).padStart(16, '0'), 'base16'),
|
|
42
|
+
topicIDs: [msg.topic],
|
|
43
|
+
signature: msg.signature,
|
|
44
|
+
key: msg.key
|
|
45
|
+
}).finish())
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
yield OkResponse()
|
|
49
|
+
yield * onMessage
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async * publish (topic: string, data: Uint8Array) {
|
|
53
|
+
this.pubsub.dispatchEvent(new CustomEvent(topic, { detail: data }))
|
|
54
|
+
yield OkResponse()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
}
|
package/src/responses.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { IResponse, Response } from '@libp2p/daemon-protocol'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates and encodes an OK response
|
|
5
|
+
*/
|
|
6
|
+
export function OkResponse (data?: Partial<IResponse>): Uint8Array {
|
|
7
|
+
return Response.encode({
|
|
8
|
+
type: Response.Type.OK,
|
|
9
|
+
...data
|
|
10
|
+
}).finish()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates and encodes an ErrorResponse
|
|
15
|
+
*/
|
|
16
|
+
export function ErrorResponse (err: Error): Uint8Array {
|
|
17
|
+
return Response.encode({
|
|
18
|
+
type: Response.Type.ERROR,
|
|
19
|
+
error: {
|
|
20
|
+
msg: err.message
|
|
21
|
+
}
|
|
22
|
+
}).finish()
|
|
23
|
+
}
|
|
@@ -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
|
+
public 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,30 @@
|
|
|
1
|
+
import type { Upgrader } from '@libp2p/interfaces/transport'
|
|
2
|
+
import type { Multiaddr } from '@multiformats/multiaddr'
|
|
3
|
+
import { resolve } from 'path'
|
|
4
|
+
import os from 'os'
|
|
5
|
+
|
|
6
|
+
export const passThroughUpgrader: Upgrader = {
|
|
7
|
+
// @ts-expect-error
|
|
8
|
+
upgradeInbound: async maConn => maConn,
|
|
9
|
+
// @ts-expect-error
|
|
10
|
+
upgradeOutbound: async maConn => maConn
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Converts the multiaddr to a nodejs NET compliant option
|
|
15
|
+
* for .connect or .listen
|
|
16
|
+
*
|
|
17
|
+
* @param {Multiaddr} addr
|
|
18
|
+
* @returns {string|object} A nodejs NET compliant option
|
|
19
|
+
*/
|
|
20
|
+
export function multiaddrToNetConfig (addr: Multiaddr) {
|
|
21
|
+
const listenPath = addr.getPath()
|
|
22
|
+
// unix socket listening
|
|
23
|
+
if (listenPath) {
|
|
24
|
+
return resolve(listenPath)
|
|
25
|
+
}
|
|
26
|
+
// tcp listening
|
|
27
|
+
return addr.nodeAddress()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const isWindows = os.platform() === 'win32'
|