@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.
@@ -0,0 +1,81 @@
1
+ import errcode from 'err-code';
2
+ import { Request, Response, PSRequest, PSMessage } from '@libp2p/daemon-protocol';
3
+ export class Pubsub {
4
+ constructor(client) {
5
+ this.client = client;
6
+ }
7
+ /**
8
+ * Get a list of topics the node is subscribed to.
9
+ *
10
+ * @returns {Array<string>} topics
11
+ */
12
+ async getTopics() {
13
+ const sh = await this.client.send({
14
+ type: Request.Type.PUBSUB,
15
+ pubsub: {
16
+ type: PSRequest.Type.GET_TOPICS
17
+ }
18
+ });
19
+ const message = await sh.read();
20
+ const response = Response.decode(message);
21
+ await sh.close();
22
+ if (response.type !== Response.Type.OK) {
23
+ throw errcode(new Error(response.error?.msg ?? 'Pubsub get topics failed'), 'ERR_PUBSUB_GET_TOPICS_FAILED');
24
+ }
25
+ if (response.pubsub == null || response.pubsub.topics == null) {
26
+ throw errcode(new Error('Invalid response'), 'ERR_PUBSUB_GET_TOPICS_FAILED');
27
+ }
28
+ return response.pubsub.topics;
29
+ }
30
+ /**
31
+ * Publish data under a topic
32
+ */
33
+ async publish(topic, data) {
34
+ if (typeof topic !== 'string') {
35
+ throw errcode(new Error('invalid topic received'), 'ERR_INVALID_TOPIC');
36
+ }
37
+ if (!(data instanceof Uint8Array)) {
38
+ throw errcode(new Error('data received is not a Uint8Array'), 'ERR_INVALID_DATA');
39
+ }
40
+ const sh = await this.client.send({
41
+ type: Request.Type.PUBSUB,
42
+ pubsub: {
43
+ type: PSRequest.Type.PUBLISH,
44
+ topic,
45
+ data
46
+ }
47
+ });
48
+ const message = await sh.read();
49
+ const response = Response.decode(message);
50
+ await sh.close();
51
+ if (response.type !== Response.Type.OK) {
52
+ throw errcode(new Error(response.error?.msg ?? 'Pubsub publish failed'), 'ERR_PUBSUB_PUBLISH_FAILED');
53
+ }
54
+ }
55
+ /**
56
+ * Request to subscribe a certain topic
57
+ */
58
+ async *subscribe(topic) {
59
+ if (typeof topic !== 'string') {
60
+ throw errcode(new Error('invalid topic received'), 'ERR_INVALID_TOPIC');
61
+ }
62
+ const sh = await this.client.send({
63
+ type: Request.Type.PUBSUB,
64
+ pubsub: {
65
+ type: PSRequest.Type.SUBSCRIBE,
66
+ topic
67
+ }
68
+ });
69
+ let message = await sh.read();
70
+ let response = Response.decode(message);
71
+ if (response.type !== Response.Type.OK) {
72
+ throw errcode(new Error(response.error?.msg ?? 'Pubsub publish failed'), 'ERR_PUBSUB_PUBLISH_FAILED');
73
+ }
74
+ // stream messages
75
+ while (true) {
76
+ message = await sh.read();
77
+ yield PSMessage.decode(message);
78
+ }
79
+ }
80
+ }
81
+ //# sourceMappingURL=pubsub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pubsub.js","sourceRoot":"","sources":["../../src/pubsub.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { Duplex } from 'it-stream-types';
2
+ export interface StreamHandlerOptions {
3
+ stream: Duplex<Uint8Array>;
4
+ maxLength?: number;
5
+ }
6
+ export declare class StreamHandler {
7
+ private stream;
8
+ private shake;
9
+ private decoder;
10
+ /**
11
+ * Create a stream handler for connection
12
+ */
13
+ constructor(opts: StreamHandlerOptions);
14
+ /**
15
+ * Read and decode message
16
+ */
17
+ read(): Promise<any>;
18
+ write(msg: Uint8Array): void;
19
+ /**
20
+ * Return the handshake rest stream and invalidate handler
21
+ */
22
+ rest(): Duplex<Uint8Array, Uint8Array, Promise<void>>;
23
+ /**
24
+ * Close the stream
25
+ */
26
+ close(): void;
27
+ }
28
+ //# sourceMappingURL=stream-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-handler.d.ts","sourceRoot":"","sources":["../../src/stream-handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAU,MAAM,iBAAiB,CAAA;AAKrD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,OAAO,CAAoB;IACnC;;OAEG;gBACU,IAAI,EAAE,oBAAoB;IAQvC;;OAEG;IACG,IAAI;IAWV,KAAK,CAAE,GAAG,EAAE,UAAU;IAOtB;;OAEG;IACH,IAAI;IAKJ;;OAEG;IACH,KAAK;CAIN"}
@@ -0,0 +1,47 @@
1
+ import * as lp from 'it-length-prefixed';
2
+ import { handshake } from 'it-handshake';
3
+ import { logger } from '@libp2p/logger';
4
+ const log = logger('libp2p:daemon-client:stream-handler');
5
+ export class StreamHandler {
6
+ /**
7
+ * Create a stream handler for connection
8
+ */
9
+ constructor(opts) {
10
+ const { stream, maxLength } = opts;
11
+ this.stream = stream;
12
+ this.shake = handshake(this.stream);
13
+ this.decoder = lp.decode.fromReader(this.shake.reader, { maxDataLength: maxLength ?? 4096 });
14
+ }
15
+ /**
16
+ * Read and decode message
17
+ */
18
+ async read() {
19
+ // @ts-expect-error decoder is really a generator
20
+ const msg = await this.decoder.next();
21
+ if (msg.value) {
22
+ return msg.value.slice();
23
+ }
24
+ log('read received no value, closing stream');
25
+ // End the stream, we didn't get data
26
+ this.close();
27
+ }
28
+ write(msg) {
29
+ log('write message');
30
+ this.shake.write(lp.encode.single(msg).slice());
31
+ }
32
+ /**
33
+ * Return the handshake rest stream and invalidate handler
34
+ */
35
+ rest() {
36
+ this.shake.rest();
37
+ return this.shake.stream;
38
+ }
39
+ /**
40
+ * Close the stream
41
+ */
42
+ close() {
43
+ log('closing the stream');
44
+ this.rest().sink([]);
45
+ }
46
+ }
47
+ //# sourceMappingURL=stream-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-handler.js","sourceRoot":"","sources":["../../src/stream-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,oBAAoB,CAAA;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAIvC,MAAM,GAAG,GAAG,MAAM,CAAC,qCAAqC,CAAC,CAAA;AAOzD,MAAM,OAAO,aAAa;IAIxB;;OAEG;IACH,YAAa,IAA0B;QACrC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAA;QAElC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACnC,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,aAAa,EAAE,SAAS,IAAI,IAAI,EAAE,CAAC,CAAA;IAC9F,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,IAAI;QACR,iDAAiD;QACjD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAA;QACrC,IAAI,GAAG,CAAC,KAAK,EAAE;YACb,OAAO,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;SACzB;QACD,GAAG,CAAC,wCAAwC,CAAC,CAAA;QAC7C,qCAAqC;QACrC,IAAI,CAAC,KAAK,EAAE,CAAA;IACd,CAAC;IAED,KAAK,CAAE,GAAe;QACpB,GAAG,CAAC,eAAe,CAAC,CAAA;QACpB,IAAI,CAAC,KAAK,CAAC,KAAK,CACd,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAC9B,CAAA;IACH,CAAC;IAED;;OAEG;IACH,IAAI;QACF,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAA;QACjB,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAA;IAC1B,CAAC;IAED;;OAEG;IACH,KAAK;QACH,GAAG,CAAC,oBAAoB,CAAC,CAAA;QACzB,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACtB,CAAC;CACF"}
@@ -0,0 +1,12 @@
1
+ import type { Upgrader } from '@libp2p/interfaces/transport';
2
+ import type { Multiaddr } from '@multiformats/multiaddr';
3
+ export declare const passThroughUpgrader: Upgrader;
4
+ /**
5
+ * Converts the multiaddr to a nodejs NET compliant option
6
+ * for .connect or .listen
7
+ *
8
+ * @param {Multiaddr} addr
9
+ * @returns {string|object} A nodejs NET compliant option
10
+ */
11
+ export declare function multiaddrToNetConfig(addr: Multiaddr): string | import("@multiformats/multiaddr").NodeAddress;
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/util/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAA;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAGxD,eAAO,MAAM,mBAAmB,EAAE,QAKjC,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAE,IAAI,EAAE,SAAS,0DAQpD"}
@@ -0,0 +1,24 @@
1
+ import { resolve } from 'path';
2
+ export const passThroughUpgrader = {
3
+ // @ts-expect-error
4
+ upgradeInbound: maConn => maConn,
5
+ // @ts-expect-error
6
+ upgradeOutbound: maConn => maConn
7
+ };
8
+ /**
9
+ * Converts the multiaddr to a nodejs NET compliant option
10
+ * for .connect or .listen
11
+ *
12
+ * @param {Multiaddr} addr
13
+ * @returns {string|object} A nodejs NET compliant option
14
+ */
15
+ export function multiaddrToNetConfig(addr) {
16
+ const listenPath = addr.getPath();
17
+ // unix socket listening
18
+ if (listenPath) {
19
+ return resolve(listenPath);
20
+ }
21
+ // tcp listening
22
+ return addr.nodeAddress();
23
+ }
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/util/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAE9B,MAAM,CAAC,MAAM,mBAAmB,GAAa;IAC3C,mBAAmB;IACnB,cAAc,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM;IAChC,mBAAmB;IACnB,eAAe,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM;CAClC,CAAA;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAE,IAAe;IACnD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,CAAA;IACjC,wBAAwB;IACxB,IAAI,UAAU,EAAE;QACd,OAAO,OAAO,CAAC,UAAU,CAAC,CAAA;KAC3B;IACD,gBAAgB;IAChB,OAAO,IAAI,CAAC,WAAW,EAAE,CAAA;AAC3B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,160 @@
1
+ {
2
+ "name": "@libp2p/daemon-client",
3
+ "version": "0.0.0",
4
+ "description": "libp2p-daemon client implementation",
5
+ "license": "Apache-2.0 OR MIT",
6
+ "homepage": "https://github.com/libp2p/js-libp2p-daemon/tree/master/packages/libp2p-daemon-client#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/libp2p/js-libp2p-daemon.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/libp2p/js-libp2p-daemon/issues"
13
+ },
14
+ "keywords": [
15
+ "libp2p"
16
+ ],
17
+ "engines": {
18
+ "node": ">=16.0.0",
19
+ "npm": ">=7.0.0"
20
+ },
21
+ "type": "module",
22
+ "types": "./dist/src/index.d.ts",
23
+ "files": [
24
+ "src",
25
+ "dist/src",
26
+ "!dist/test",
27
+ "!**/*.tsbuildinfo"
28
+ ],
29
+ "exports": {
30
+ ".": {
31
+ "import": "./dist/src/index.js"
32
+ }
33
+ },
34
+ "eslintConfig": {
35
+ "extends": "ipfs",
36
+ "parserOptions": {
37
+ "sourceType": "module"
38
+ }
39
+ },
40
+ "release": {
41
+ "branches": [
42
+ "master"
43
+ ],
44
+ "plugins": [
45
+ [
46
+ "@semantic-release/commit-analyzer",
47
+ {
48
+ "preset": "conventionalcommits",
49
+ "releaseRules": [
50
+ {
51
+ "breaking": true,
52
+ "release": "major"
53
+ },
54
+ {
55
+ "revert": true,
56
+ "release": "patch"
57
+ },
58
+ {
59
+ "type": "feat",
60
+ "release": "minor"
61
+ },
62
+ {
63
+ "type": "fix",
64
+ "release": "patch"
65
+ },
66
+ {
67
+ "type": "chore",
68
+ "release": "patch"
69
+ },
70
+ {
71
+ "type": "docs",
72
+ "release": "patch"
73
+ },
74
+ {
75
+ "type": "test",
76
+ "release": "patch"
77
+ },
78
+ {
79
+ "scope": "no-release",
80
+ "release": false
81
+ }
82
+ ]
83
+ }
84
+ ],
85
+ [
86
+ "@semantic-release/release-notes-generator",
87
+ {
88
+ "preset": "conventionalcommits",
89
+ "presetConfig": {
90
+ "types": [
91
+ {
92
+ "type": "feat",
93
+ "section": "Features"
94
+ },
95
+ {
96
+ "type": "fix",
97
+ "section": "Bug Fixes"
98
+ },
99
+ {
100
+ "type": "chore",
101
+ "section": "Trivial Changes"
102
+ },
103
+ {
104
+ "type": "docs",
105
+ "section": "Trivial Changes"
106
+ },
107
+ {
108
+ "type": "test",
109
+ "section": "Tests"
110
+ }
111
+ ]
112
+ }
113
+ }
114
+ ],
115
+ "@semantic-release/changelog",
116
+ "@semantic-release/npm",
117
+ "@semantic-release/github",
118
+ "@semantic-release/git"
119
+ ]
120
+ },
121
+ "scripts": {
122
+ "lint": "aegir lint",
123
+ "build": "tsc",
124
+ "pretest": "npm run build",
125
+ "test": "aegir test -t node -f ./dist/test/*.js",
126
+ "test:node": "aegir test -t node -f ./dist/test/*.js",
127
+ "release": "semantic-release"
128
+ },
129
+ "dependencies": {
130
+ "@libp2p/daemon-protocol": "^0.0.0",
131
+ "@libp2p/interfaces": "^1.3.17",
132
+ "@libp2p/logger": "^1.1.2",
133
+ "@libp2p/peer-id": "^1.1.8",
134
+ "@multiformats/multiaddr": "^10.1.8",
135
+ "err-code": "^3.0.1",
136
+ "it-handshake": "^3.0.1",
137
+ "it-length-prefixed": "^7.0.1",
138
+ "it-stream-types": "^1.0.4",
139
+ "libp2p-daemon": "^0.10.0",
140
+ "libp2p-tcp": "^0.17.1",
141
+ "multiaddr": "^10.0.0",
142
+ "multiformats": "^9.4.2",
143
+ "peer-id": "^0.16.0"
144
+ },
145
+ "devDependencies": {
146
+ "@libp2p/peer-id-factory": "^1.0.8",
147
+ "@libp2p/tcp": "^1.0.6",
148
+ "aegir": "^36.0.0",
149
+ "it-buffer": "^0.1.3",
150
+ "it-pipe": "^2.0.3",
151
+ "it-pushable": "^2.0.1",
152
+ "mocha": "^9.1.1",
153
+ "net": "^1.0.2",
154
+ "os": "^0.1.2",
155
+ "path": "^0.12.7",
156
+ "sinon": "^13.0.1",
157
+ "streaming-iterables": "^6.0.0",
158
+ "uint8arrays": "^3.0.0"
159
+ }
160
+ }
package/src/dht.ts ADDED
@@ -0,0 +1,285 @@
1
+ import { CID } from 'multiformats/cid'
2
+ import { Multiaddr } from '@multiformats/multiaddr'
3
+ import errcode from 'err-code'
4
+ import {
5
+ Request,
6
+ Response,
7
+ DHTRequest,
8
+ DHTResponse
9
+ } from '@libp2p/daemon-protocol'
10
+ import type { DaemonClient } from './index.js'
11
+ import { isPeerId, PeerId } from '@libp2p/interfaces/peer-id'
12
+ import type { PeerData } from '@libp2p/interfaces/peer-data'
13
+ import { peerIdFromBytes } from '@libp2p/peer-id'
14
+
15
+ export class DHT {
16
+ private client: DaemonClient
17
+
18
+ constructor (client: DaemonClient) {
19
+ this.client = client
20
+ }
21
+
22
+ /**
23
+ * Write a value to a key in the DHT
24
+ */
25
+ async put (key: Uint8Array, value: Uint8Array): Promise<void> {
26
+ if (!(key instanceof Uint8Array)) {
27
+ throw errcode(new Error('invalid key received'), 'ERR_INVALID_KEY')
28
+ }
29
+
30
+ if (!(value instanceof Uint8Array)) {
31
+ throw errcode(new Error('value received is not a Uint8Array'), 'ERR_INVALID_VALUE')
32
+ }
33
+
34
+ const sh = await this.client.send({
35
+ type: Request.Type.DHT,
36
+ dht: {
37
+ type: DHTRequest.Type.PUT_VALUE,
38
+ key,
39
+ value
40
+ }
41
+ })
42
+
43
+ const message = await sh.read()
44
+ const response = Response.decode(message)
45
+
46
+ await sh.close()
47
+
48
+ if (response.type !== Response.Type.OK) {
49
+ throw errcode(new Error(response.error?.msg ?? 'DHT put failed'), 'ERR_DHT_PUT_FAILED')
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Query the DHT for a value stored at a key in the DHT
55
+ */
56
+ async get (key: Uint8Array): Promise<Uint8Array> {
57
+ if (!(key instanceof Uint8Array)) {
58
+ throw errcode(new Error('invalid key received'), 'ERR_INVALID_KEY')
59
+ }
60
+
61
+ const sh = await this.client.send({
62
+ type: Request.Type.DHT,
63
+ dht: {
64
+ type: DHTRequest.Type.GET_VALUE,
65
+ key
66
+ }
67
+ })
68
+
69
+ const message = await sh.read()
70
+ const response = Response.decode(message)
71
+
72
+ await sh.close()
73
+
74
+ if (response.type !== Response.Type.OK) {
75
+ throw errcode(new Error(response.error?.msg ?? 'DHT get failed'), 'ERR_DHT_GET_FAILED')
76
+ }
77
+
78
+ if (response.dht == null || response.dht.value == null) {
79
+ throw errcode(new Error('Invalid DHT get response'), 'ERR_DHT_GET_FAILED')
80
+ }
81
+
82
+ return response.dht.value
83
+ }
84
+
85
+ /**
86
+ * Query the DHT for a given peer's known addresses.
87
+ *
88
+ * @param {PeerId} peerId
89
+ * @returns {PeerInfo}
90
+ */
91
+ async findPeer (peerId: PeerId): Promise<PeerData> {
92
+ if (!isPeerId(peerId)) {
93
+ throw errcode(new Error('invalid peer id received'), 'ERR_INVALID_PEER_ID')
94
+ }
95
+
96
+ const sh = await this.client.send({
97
+ type: Request.Type.DHT,
98
+ dht: {
99
+ type: DHTRequest.Type.FIND_PEER,
100
+ peer: peerId.toBytes()
101
+ }
102
+ })
103
+
104
+ const message = await sh.read()
105
+ const response = Response.decode(message)
106
+
107
+ await sh.close()
108
+
109
+ if (response.type !== Response.Type.OK) {
110
+ throw errcode(new Error(response.error?.msg ?? 'DHT find peer failed'), 'ERR_DHT_FIND_PEER_FAILED')
111
+ }
112
+
113
+ if (response.dht == null || response.dht.peer == null || response.dht.peer.addrs == null) {
114
+ throw errcode(new Error('Invalid response'), 'ERR_DHT_FIND_PEER_FAILED')
115
+ }
116
+
117
+ return {
118
+ id: peerIdFromBytes(response.dht.peer.id),
119
+ multiaddrs: response.dht.peer.addrs.map((a) => new Multiaddr(a)),
120
+ protocols: []
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Announce to the network that the peer have data addressed by the provided CID
126
+ */
127
+ async provide (cid: CID) {
128
+ if (cid == null || CID.asCID(cid) == null) {
129
+ throw errcode(new Error('invalid cid received'), 'ERR_INVALID_CID')
130
+ }
131
+
132
+ const sh = await this.client.send({
133
+ type: Request.Type.DHT,
134
+ dht: {
135
+ type: DHTRequest.Type.PROVIDE,
136
+ cid: cid.bytes
137
+ }
138
+ })
139
+
140
+ const message = await sh.read()
141
+ const response = Response.decode(message)
142
+
143
+ await sh.close()
144
+
145
+ if (response.type !== Response.Type.OK) {
146
+ throw errcode(new Error(response.error?.msg ?? 'DHT provide failed'), 'ERR_DHT_PROVIDE_FAILED')
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Query the DHT for peers that have a piece of content, identified by a CID
152
+ */
153
+ async * findProviders (cid: CID, count: number = 1): AsyncIterator<PeerData> {
154
+ if (cid == null || CID.asCID(cid) == null) {
155
+ throw errcode(new Error('invalid cid received'), 'ERR_INVALID_CID')
156
+ }
157
+
158
+ const sh = await this.client.send({
159
+ type: Request.Type.DHT,
160
+ dht: {
161
+ type: DHTRequest.Type.FIND_PROVIDERS,
162
+ cid: cid.bytes,
163
+ count
164
+ }
165
+ })
166
+
167
+ let message = await sh.read()
168
+
169
+ // stream begin message
170
+ let response = Response.decode(message)
171
+
172
+ if (response.type !== Response.Type.OK) {
173
+ await sh.close()
174
+ throw errcode(new Error(response.error?.msg || 'DHT find providers failed'), 'ERR_DHT_FIND_PROVIDERS_FAILED')
175
+ }
176
+
177
+ while (true) {
178
+ message = await sh.read()
179
+ const response = DHTResponse.decode(message)
180
+
181
+ // Stream end
182
+ if (response.type === DHTResponse.Type.END) {
183
+ await sh.close()
184
+ return
185
+ }
186
+
187
+ // Stream values
188
+ if (response.type === DHTResponse.Type.VALUE && response.peer != null && response.peer?.addrs != null) {
189
+ yield {
190
+ id: peerIdFromBytes(response.peer.id),
191
+ multiaddrs: response.peer.addrs.map((a) => new Multiaddr(a)),
192
+ protocols: []
193
+ }
194
+ } else {
195
+ // Unexpected message received
196
+ await sh.close()
197
+ throw errcode(new Error('unexpected message received'), 'ERR_UNEXPECTED_MESSAGE_RECEIVED')
198
+ }
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Query the DHT routing table for peers that are closest to a provided key.
204
+ */
205
+ async * getClosestPeers (key: Uint8Array): AsyncIterable<PeerData> {
206
+ if (!(key instanceof Uint8Array)) {
207
+ throw errcode(new Error('invalid key received'), 'ERR_INVALID_KEY')
208
+ }
209
+
210
+ const sh = await this.client.send({
211
+ type: Request.Type.DHT,
212
+ dht: {
213
+ type: DHTRequest.Type.GET_CLOSEST_PEERS,
214
+ key
215
+ }
216
+ })
217
+
218
+ // stream begin message
219
+ let message = await sh.read()
220
+ let response = Response.decode(message)
221
+
222
+ if (response.type !== Response.Type.OK) {
223
+ await sh.close()
224
+ throw errcode(new Error(response.error?.msg ?? 'DHT find providers failed'), 'ERR_DHT_FIND_PROVIDERS_FAILED')
225
+ }
226
+
227
+ while (true) {
228
+ message = await sh.read()
229
+ const response = DHTResponse.decode(message)
230
+
231
+ // Stream end
232
+ if (response.type === DHTResponse.Type.END) {
233
+ await sh.close()
234
+ return
235
+ }
236
+
237
+ // Stream values
238
+ if (response.type === DHTResponse.Type.VALUE && response.value != null) {
239
+ const peerId = peerIdFromBytes(response.value)
240
+
241
+ yield {
242
+ id: peerId,
243
+ multiaddrs: [],
244
+ protocols: []
245
+ }
246
+ } else {
247
+ // Unexpected message received
248
+ await sh.close()
249
+ throw errcode(new Error('unexpected message received'), 'ERR_UNEXPECTED_MESSAGE_RECEIVED')
250
+ }
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Query the DHT routing table for a given peer's public key.
256
+ */
257
+ async getPublicKey (peerId: PeerId) {
258
+ if (!isPeerId(peerId)) {
259
+ throw errcode(new Error('invalid peer id received'), 'ERR_INVALID_PEER_ID')
260
+ }
261
+
262
+ const sh = await this.client.send({
263
+ type: Request.Type.DHT,
264
+ dht: {
265
+ type: DHTRequest.Type.GET_PUBLIC_KEY,
266
+ peer: peerId.toBytes()
267
+ }
268
+ })
269
+
270
+ const message = await sh.read()
271
+ const response = Response.decode(message)
272
+
273
+ await sh.close()
274
+
275
+ if (response.type !== Response.Type.OK) {
276
+ throw errcode(new Error(response.error?.msg ?? 'DHT get public key failed'), 'ERR_DHT_GET_PUBLIC_KEY_FAILED')
277
+ }
278
+
279
+ if (response.dht == null) {
280
+ throw errcode(new Error('Invalid response'), 'ERR_DHT_GET_PUBLIC_KEY_FAILED')
281
+ }
282
+
283
+ return response.dht.value
284
+ }
285
+ }