opendht 0.0.0-stage → 4.4.101
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/README.md +242 -2
- package/crypto.js +56 -0
- package/examples/dhtnode.js +176 -0
- package/examples/dhtnode.ts +171 -0
- package/index.d.ts +120 -0
- package/index.js +187 -0
- package/package.json +57 -5
- package/prebuilds/darwin-arm64/libargon2.1.dylib +0 -0
- package/prebuilds/darwin-arm64/libcrypto.3.dylib +0 -0
- package/prebuilds/darwin-arm64/libfmt.12.2.0.dylib +0 -0
- package/prebuilds/darwin-arm64/libgmp.10.dylib +0 -0
- package/prebuilds/darwin-arm64/libgnutls.30.dylib +0 -0
- package/prebuilds/darwin-arm64/libhogweed.7.0.dylib +0 -0
- package/prebuilds/darwin-arm64/libidn2.0.dylib +0 -0
- package/prebuilds/darwin-arm64/libintl.8.dylib +0 -0
- package/prebuilds/darwin-arm64/libjsoncpp.27.dylib +0 -0
- package/prebuilds/darwin-arm64/libllhttp.9.4.3.dylib +0 -0
- package/prebuilds/darwin-arm64/libnettle.9.0.dylib +0 -0
- package/prebuilds/darwin-arm64/libopendht.4.4.99.dylib +0 -0
- package/prebuilds/darwin-arm64/libp11-kit.0.dylib +0 -0
- package/prebuilds/darwin-arm64/libsimdutf.35.0.0.dylib +0 -0
- package/prebuilds/darwin-arm64/libssl.3.dylib +0 -0
- package/prebuilds/darwin-arm64/libtasn1.6.dylib +0 -0
- package/prebuilds/darwin-arm64/libunistring.5.dylib +0 -0
- package/prebuilds/darwin-arm64/licenses/OpenDHT-LICENSE.txt +7 -0
- package/prebuilds/darwin-arm64/licenses/argon2-LICENSE.txt +314 -0
- package/prebuilds/darwin-arm64/licenses/fmt-LICENSE.txt +20 -0
- package/prebuilds/darwin-arm64/licenses/gettext-COPYING.txt +674 -0
- package/prebuilds/darwin-arm64/licenses/gmp-COPYING.LESSERv3.txt +165 -0
- package/prebuilds/darwin-arm64/licenses/gmp-COPYING.txt +674 -0
- package/prebuilds/darwin-arm64/licenses/gnutls-COPYING.LESSERv2.txt +501 -0
- package/prebuilds/darwin-arm64/licenses/gnutls-COPYING.txt +674 -0
- package/prebuilds/darwin-arm64/licenses/jsoncpp-LICENSE.txt +55 -0
- package/prebuilds/darwin-arm64/licenses/libidn2-COPYING.LESSERv3.txt +165 -0
- package/prebuilds/darwin-arm64/licenses/libidn2-COPYING.txt +674 -0
- package/prebuilds/darwin-arm64/licenses/libidn2-COPYING.unicode.txt +91 -0
- package/prebuilds/darwin-arm64/licenses/libtasn1-COPYING.LESSERv2.txt +501 -0
- package/prebuilds/darwin-arm64/licenses/libtasn1-COPYING.txt +674 -0
- package/prebuilds/darwin-arm64/licenses/libunistring-COPYING.LIB.txt +165 -0
- package/prebuilds/darwin-arm64/licenses/libunistring-COPYING.txt +674 -0
- package/prebuilds/darwin-arm64/licenses/llhttp-LICENSE-MIT.txt +22 -0
- package/prebuilds/darwin-arm64/licenses/llhttp-LICENSE.txt +22 -0
- package/prebuilds/darwin-arm64/licenses/nettle-COPYING.LESSERv3.txt +165 -0
- package/prebuilds/darwin-arm64/licenses/openssl@3-LICENSE.txt.txt +177 -0
- package/prebuilds/darwin-arm64/licenses/p11-kit-COPYING.txt +27 -0
- package/prebuilds/darwin-arm64/licenses/simdutf-LICENSE-APACHE.txt +201 -0
- package/prebuilds/darwin-arm64/licenses/simdutf-LICENSE-MIT.txt +18 -0
- package/prebuilds/darwin-arm64/manifest.json +25 -0
- package/prebuilds/darwin-arm64/node.napi.node +0 -0
- package/prebuilds/darwin-x64/libargon2.1.dylib +0 -0
- package/prebuilds/darwin-x64/libcrypto.3.dylib +0 -0
- package/prebuilds/darwin-x64/libfmt.12.2.0.dylib +0 -0
- package/prebuilds/darwin-x64/libgmp.10.dylib +0 -0
- package/prebuilds/darwin-x64/libgnutls.30.dylib +0 -0
- package/prebuilds/darwin-x64/libhogweed.7.0.dylib +0 -0
- package/prebuilds/darwin-x64/libidn2.0.dylib +0 -0
- package/prebuilds/darwin-x64/libintl.8.dylib +0 -0
- package/prebuilds/darwin-x64/libjsoncpp.27.dylib +0 -0
- package/prebuilds/darwin-x64/libllhttp.9.4.3.dylib +0 -0
- package/prebuilds/darwin-x64/libnettle.9.0.dylib +0 -0
- package/prebuilds/darwin-x64/libopendht.4.4.99.dylib +0 -0
- package/prebuilds/darwin-x64/libp11-kit.0.dylib +0 -0
- package/prebuilds/darwin-x64/libsimdutf.35.0.0.dylib +0 -0
- package/prebuilds/darwin-x64/libssl.3.dylib +0 -0
- package/prebuilds/darwin-x64/libtasn1.6.dylib +0 -0
- package/prebuilds/darwin-x64/libunistring.5.dylib +0 -0
- package/prebuilds/darwin-x64/licenses/OpenDHT-LICENSE.txt +7 -0
- package/prebuilds/darwin-x64/licenses/argon2-LICENSE.txt +314 -0
- package/prebuilds/darwin-x64/licenses/fmt-LICENSE.txt +20 -0
- package/prebuilds/darwin-x64/licenses/gettext-COPYING.txt +674 -0
- package/prebuilds/darwin-x64/licenses/gmp-COPYING.LESSERv3.txt +165 -0
- package/prebuilds/darwin-x64/licenses/gmp-COPYING.txt +674 -0
- package/prebuilds/darwin-x64/licenses/gnutls-COPYING.LESSERv2.txt +501 -0
- package/prebuilds/darwin-x64/licenses/gnutls-COPYING.txt +674 -0
- package/prebuilds/darwin-x64/licenses/jsoncpp-LICENSE.txt +55 -0
- package/prebuilds/darwin-x64/licenses/libidn2-COPYING.LESSERv3.txt +165 -0
- package/prebuilds/darwin-x64/licenses/libidn2-COPYING.txt +674 -0
- package/prebuilds/darwin-x64/licenses/libidn2-COPYING.unicode.txt +91 -0
- package/prebuilds/darwin-x64/licenses/libtasn1-COPYING.LESSERv2.txt +501 -0
- package/prebuilds/darwin-x64/licenses/libtasn1-COPYING.txt +674 -0
- package/prebuilds/darwin-x64/licenses/libunistring-COPYING.LIB.txt +165 -0
- package/prebuilds/darwin-x64/licenses/libunistring-COPYING.txt +674 -0
- package/prebuilds/darwin-x64/licenses/llhttp-LICENSE-MIT.txt +22 -0
- package/prebuilds/darwin-x64/licenses/llhttp-LICENSE.txt +22 -0
- package/prebuilds/darwin-x64/licenses/nettle-COPYING.LESSERv3.txt +165 -0
- package/prebuilds/darwin-x64/licenses/openssl@3-LICENSE.txt.txt +177 -0
- package/prebuilds/darwin-x64/licenses/p11-kit-COPYING.txt +27 -0
- package/prebuilds/darwin-x64/licenses/simdutf-LICENSE-APACHE.txt +201 -0
- package/prebuilds/darwin-x64/licenses/simdutf-LICENSE-MIT.txt +18 -0
- package/prebuilds/darwin-x64/manifest.json +25 -0
- package/prebuilds/darwin-x64/node.napi.node +0 -0
- package/prebuilds/linux-x64/libargon2.so.1 +0 -0
- package/prebuilds/linux-x64/libcrypto.so.3 +0 -0
- package/prebuilds/linux-x64/libffi.so.8 +0 -0
- package/prebuilds/linux-x64/libfmt.so.9 +0 -0
- package/prebuilds/linux-x64/libgmp.so.10 +0 -0
- package/prebuilds/linux-x64/libgnutls.so.30 +0 -0
- package/prebuilds/linux-x64/libhogweed.so.6 +0 -0
- package/prebuilds/linux-x64/libidn2.so.0 +0 -0
- package/prebuilds/linux-x64/libjsoncpp.so.25 +0 -0
- package/prebuilds/linux-x64/libllhttp.so.9.2 +0 -0
- package/prebuilds/linux-x64/libnettle.so.8 +0 -0
- package/prebuilds/linux-x64/libopendht.so.4 +0 -0
- package/prebuilds/linux-x64/libp11-kit.so.0 +0 -0
- package/prebuilds/linux-x64/libssl.so.3 +0 -0
- package/prebuilds/linux-x64/libtasn1.so.6 +0 -0
- package/prebuilds/linux-x64/libunistring.so.5 +0 -0
- package/prebuilds/linux-x64/licenses/OpenDHT-LICENSE.txt +7 -0
- package/prebuilds/linux-x64/licenses/libargon2-1-copyright.txt +167 -0
- package/prebuilds/linux-x64/licenses/libffi8-copyright.txt +225 -0
- package/prebuilds/linux-x64/licenses/libfmt9-copyright.txt +411 -0
- package/prebuilds/linux-x64/licenses/libgmp10-copyright.txt +94 -0
- package/prebuilds/linux-x64/licenses/libgnutls30t64-copyright.txt +1248 -0
- package/prebuilds/linux-x64/licenses/libhogweed6t64-copyright.txt +723 -0
- package/prebuilds/linux-x64/licenses/libidn2-0-copyright.txt +113 -0
- package/prebuilds/linux-x64/licenses/libjsoncpp25-copyright.txt +84 -0
- package/prebuilds/linux-x64/licenses/libnettle8t64-copyright.txt +723 -0
- package/prebuilds/linux-x64/licenses/libp11-kit0-copyright.txt +493 -0
- package/prebuilds/linux-x64/licenses/libssl3t64-copyright.txt +70 -0
- package/prebuilds/linux-x64/licenses/libtasn1-6-copyright.txt +83 -0
- package/prebuilds/linux-x64/licenses/libunistring5-copyright.txt +152 -0
- package/prebuilds/linux-x64/licenses/llhttp-llhttp-LICENSE-MIT.txt +22 -0
- package/prebuilds/linux-x64/licenses/simdutf/LICENSE-APACHE +201 -0
- package/prebuilds/linux-x64/licenses/simdutf/LICENSE-MIT +18 -0
- package/prebuilds/linux-x64/manifest.json +24 -0
- package/prebuilds/linux-x64/node.napi.glibc.node +0 -0
- package/prebuilds/win32-x64/argon2.dll +0 -0
- package/prebuilds/win32-x64/fmt.dll +0 -0
- package/prebuilds/win32-x64/gmp-10.dll +0 -0
- package/prebuilds/win32-x64/gnutls.dll +0 -0
- package/prebuilds/win32-x64/hogweed-7.dll +0 -0
- package/prebuilds/win32-x64/jsoncpp.dll +0 -0
- package/prebuilds/win32-x64/libcrypto-3-x64.dll +0 -0
- package/prebuilds/win32-x64/libssl-3-x64.dll +0 -0
- package/prebuilds/win32-x64/licenses/OpenDHT-LICENSE.txt +7 -0
- package/prebuilds/win32-x64/manifest.json +20 -0
- package/prebuilds/win32-x64/nettle-9.dll +0 -0
- package/prebuilds/win32-x64/node.napi.node +0 -0
- package/prebuilds/win32-x64/opendht.dll +0 -0
- package/prebuilds/win32-x64/simdutf.dll +0 -0
- package/prebuilds/win32-x64/tasn1-6.dll +0 -0
package/README.md
CHANGED
|
@@ -1,3 +1,243 @@
|
|
|
1
|
-
#
|
|
1
|
+
# OpenDHT Node.js Bindings
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Initial Node-API bindings using `node-addon-api` directly over the C++ library.
|
|
4
|
+
Node.js 22.9 or later is required; the addon targets Node-API version 8.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
npm install opendht
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Build
|
|
13
|
+
|
|
14
|
+
Install the native dependencies described in [BUILD.md](../BUILD.md), plus Node.js,
|
|
15
|
+
a C++17 compiler, and CMake 3.21 or later. Ninja is optional. Proxy-client support
|
|
16
|
+
defaults to enabled and requires ASIO, llhttp, RESTinio, and expected-lite headers.
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
cd nodejs
|
|
20
|
+
npm ci
|
|
21
|
+
npm run build
|
|
22
|
+
npm test
|
|
23
|
+
npm run typecheck
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
CMake.js builds OpenDHT from the parent source directory in `nodejs/build/native`.
|
|
27
|
+
It does not change the repository's existing build directory. Tools, Python,
|
|
28
|
+
peer discovery, documentation, and native tests default to disabled in this build.
|
|
29
|
+
Native CMake options can be passed through, for example:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
npm run build -- --CDOPENDHT_PEER_DISCOVERY=ON
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
For an existing build, change cached options directly with CMake, for example
|
|
36
|
+
`cmake -S . -B build -DOPENDHT_PROXY_CLIENT=ON`, then rebuild. A custom build can
|
|
37
|
+
disable proxy support with `-DOPENDHT_PROXY_CLIENT=OFF`; requesting a proxy then
|
|
38
|
+
throws an explicit unsupported-build error.
|
|
39
|
+
|
|
40
|
+
The loader checks local builds, then compatible Node-API binaries under `prebuilds/`.
|
|
41
|
+
Set `OPENDHT_NODE_BINDING` to an absolute addon path to select an explicit build.
|
|
42
|
+
Packaged binaries include OpenDHT and non-system native dependencies, so archive
|
|
43
|
+
installation does not require a compiler or a system OpenDHT installation.
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
import { DhtRunner, hash } from 'opendht';
|
|
49
|
+
|
|
50
|
+
async function main() {
|
|
51
|
+
const writer = new DhtRunner();
|
|
52
|
+
const reader = new DhtRunner();
|
|
53
|
+
try {
|
|
54
|
+
writer.run({ port: 0, ipv4: '127.0.0.1', ipv6: '::1' });
|
|
55
|
+
reader.run({ port: 0, ipv4: '127.0.0.1', ipv6: '::1' });
|
|
56
|
+
writer.bootstrap('127.0.0.1', reader.getBoundPort());
|
|
57
|
+
reader.bootstrap('127.0.0.1', writer.getBoundPort());
|
|
58
|
+
const key = hash('example');
|
|
59
|
+
const stored = await writer.put(key, Buffer.from('hello'), { id: 42n });
|
|
60
|
+
if (!stored) throw new Error('DHT put failed');
|
|
61
|
+
for await (const value of reader.get(key)) {
|
|
62
|
+
console.log(value.id, value.data.toString());
|
|
63
|
+
}
|
|
64
|
+
} finally {
|
|
65
|
+
await Promise.all([writer.close(), reader.close()]);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
main().catch(console.error);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## API
|
|
73
|
+
|
|
74
|
+
- `hash(string | Uint8Array)` returns a SHA-1 hash as a 40-character hex string.
|
|
75
|
+
All get/put/listen keys must be hex hashes, not unhashed application keys.
|
|
76
|
+
- `version` is the underlying OpenDHT version.
|
|
77
|
+
- `run({port, ipv4, ipv6, network, persistPath, identity})` starts a threaded node. Port 0
|
|
78
|
+
selects a free port. The defaults bind both IP families on all interfaces.
|
|
79
|
+
`persistPath` is optional; use an empty string to disable persistence.
|
|
80
|
+
`proxyServer` and `proxyUserAgent` configure proxy mode (see below).
|
|
81
|
+
- `getBoundPort(4 | 6)` defaults to IPv4; `getNodeId()` returns the node's hex ID.
|
|
82
|
+
- `getStatus()` returns the latest JS-dispatched `{ipv4, ipv6}` connectivity
|
|
83
|
+
snapshot. Each family is `'disconnected'`, `'connecting'`, or `'connected'`.
|
|
84
|
+
`runner.on('statusChanged', status => {})` receives changes on the JS thread.
|
|
85
|
+
New and closing/closed runners report both families as disconnected. No status
|
|
86
|
+
events are delivered after close begins; close does not emit a final event.
|
|
87
|
+
- `bootstrap(host, port)` queues bootstrap discovery. Returning does not mean the
|
|
88
|
+
peer is connected; subsequent DHT operations report their own completion.
|
|
89
|
+
- `put(key, Uint8Array, {id, type, userType, permanent})` copies the input and
|
|
90
|
+
resolves to the native success boolean. IDs are unsigned 64-bit `bigint` values.
|
|
91
|
+
- `get(key)` returns an async iterator of values as they arrive. The iterator
|
|
92
|
+
ends when the lookup completes, or rejects with `ERR_DHT_GET_FAILED` on failure.
|
|
93
|
+
`getAll(key)` returns a single promise resolving to the complete value array.
|
|
94
|
+
Values contain `id`, `type`, `seq`, `userType`, copied `Buffer` data, `isSigned`,
|
|
95
|
+
`isEncrypted`, and an `owner` long fingerprint (or null for unsigned values).
|
|
96
|
+
- `listen(key, (value, expired) => {})` returns a listener with idempotent
|
|
97
|
+
`cancel()`. Queued deliveries are suppressed after cancellation. Callback
|
|
98
|
+
return values are ignored; callbacks may cancel or close the runner.
|
|
99
|
+
- `listen(key)` without a callback returns an async iterator of `{value, expired}`
|
|
100
|
+
events. It continues until cancelled or the runner closes.
|
|
101
|
+
- `close()` is idempotent and returns the same promise on repeated calls. It
|
|
102
|
+
waits off the JS thread for graceful shutdown, then joins the native threads.
|
|
103
|
+
After two seconds it forces the join if graceful shutdown has not completed.
|
|
104
|
+
Pending operations reject with `ERR_DHT_CLOSED`. A closed runner cannot restart.
|
|
105
|
+
- `ref()` and `unref()` control whether the runner keeps Node's event loop alive.
|
|
106
|
+
Running nodes are referenced by default. `Symbol.asyncDispose` calls `close()`.
|
|
107
|
+
|
|
108
|
+
Callbacks run only on their owning JS thread. There is no polling timer or worker
|
|
109
|
+
thread waiting on each get/put. Explicitly close runners; GC cleanup and Node
|
|
110
|
+
environment cleanup hooks are fallbacks, including forced worker termination.
|
|
111
|
+
Pending operations, active listeners, and registered status handlers keep their
|
|
112
|
+
runner alive until completion, cancellation, handler removal, or explicit close.
|
|
113
|
+
Graceful persistence cannot be guaranteed if the process is killed.
|
|
114
|
+
|
|
115
|
+
The binding does not cap the callback queue, accumulated `getAll()` results, or
|
|
116
|
+
iterator buffers. Slow iterator consumers can accumulate values without
|
|
117
|
+
backpressure. Queued callbacks retain their native values until delivery or cleanup.
|
|
118
|
+
|
|
119
|
+
## Async Iteration
|
|
120
|
+
|
|
121
|
+
`get()` and callback-free `listen()` start immediately and return iterators with
|
|
122
|
+
`next()`, `return()`, `throw()`, `cancel()`, and `Symbol.asyncDispose`.
|
|
123
|
+
Leaving a `for await` loop with `break` or an exception cancels the subscription.
|
|
124
|
+
Explicit cancellation discards buffered values and settles pending reads as done.
|
|
125
|
+
Closing the runner rejects active get iterators with `ERR_DHT_CLOSED`; listener
|
|
126
|
+
iterators end normally. Successful get completion preserves unread buffered values.
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
for await (const value of runner.get(hash('example'))) {
|
|
130
|
+
console.log(value.id, value.data);
|
|
131
|
+
}
|
|
132
|
+
const snapshots = await runner.getAll(hash('example'));
|
|
133
|
+
|
|
134
|
+
for await (const { value, expired } of runner.listen(hash('updates'))) {
|
|
135
|
+
console.log(value.id, expired);
|
|
136
|
+
if (expired) break;
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Proxy Configuration
|
|
141
|
+
|
|
142
|
+
Pass the URL of an OpenDHT HTTP(S) proxy at startup:
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
runner.run({
|
|
146
|
+
proxyServer: 'https://proxy.example.com',
|
|
147
|
+
proxyUserAgent: 'my-app/1.0',
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
This selects the native OpenDHT proxy client, not an HTTP CONNECT or SOCKS proxy.
|
|
152
|
+
The node does not bind UDP sockets in this mode; `port`, `ipv4`, `ipv6`, and
|
|
153
|
+
`bootstrap()` are for direct DHT nodes. Omit `proxyServer` or use an empty string
|
|
154
|
+
for direct networking. Proxy settings are startup-only; create a new runner to
|
|
155
|
+
change transports or servers. Packaged binaries include proxy-client support.
|
|
156
|
+
|
|
157
|
+
## Crypto
|
|
158
|
+
|
|
159
|
+
`PrivateKey.generate(options)` and `Identity.generate(name, options)` return
|
|
160
|
+
promises and generate keys off the JS thread. Options accept `algorithm: 'ec'`
|
|
161
|
+
(the default, using OpenDHT's default EC curve) or `'rsa'`. RSA `bits` must be an
|
|
162
|
+
integer between 2048 and 8192; the default is 2048.
|
|
163
|
+
|
|
164
|
+
- `new PrivateKey(bytes, password?)` imports PEM/DER; `serialize(password?)`
|
|
165
|
+
exports PKCS#8 PEM. A nonempty password encrypts the exported key.
|
|
166
|
+
- `privateKey.getPublicKey()` returns an owned public key. `sign(bytes)` and
|
|
167
|
+
`decrypt(bytes)` return copied Buffers.
|
|
168
|
+
- `new PublicKey(bytes)` imports a public key; `serialize()` exports DER.
|
|
169
|
+
`checkSignature(data, signature)` returns a boolean. `encrypt(bytes)` requires
|
|
170
|
+
an RSA key; EC keys support signatures, not this encryption scheme.
|
|
171
|
+
- `new Certificate(bytes)` imports PEM/DER or a PEM certificate chain;
|
|
172
|
+
`serialize()` exports a PEM chain. Import does not establish trust or validate
|
|
173
|
+
the chain against a trust store. `getPublicKey()` returns its public key.
|
|
174
|
+
- Public keys and certificates expose `getId()` (40 hex characters) and
|
|
175
|
+
`getLongId()` (64 hex characters).
|
|
176
|
+
- `new Identity(privateKey, certificate)` requires matching fingerprints.
|
|
177
|
+
Its read-only `privateKey` and `certificate` properties return owned wrappers.
|
|
178
|
+
- Pass `identity` to `run()` to enable `putSigned(key, data, options?)` and
|
|
179
|
+
`putEncrypted(key, data, recipientPublicKey, options?)`. Both return the same
|
|
180
|
+
success boolean as `put()`. Encrypted values are signed by the sender and
|
|
181
|
+
transparently decrypted for the recipient by OpenDHT's get/listen layer.
|
|
182
|
+
|
|
183
|
+
```js
|
|
184
|
+
import { Identity, DhtRunner } from 'opendht';
|
|
185
|
+
|
|
186
|
+
async function createAuthenticatedNode() {
|
|
187
|
+
const identity = await Identity.generate('example', { algorithm: 'rsa', bits: 2048 });
|
|
188
|
+
const runner = new DhtRunner();
|
|
189
|
+
runner.run({ port: 0, identity });
|
|
190
|
+
return { runner, identity };
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Crypto methods take Uint8Array/Buffer inputs, not implicit text encodings. Imports
|
|
195
|
+
must be nonempty. Malformed handles/data,
|
|
196
|
+
mismatched identities, and unsupported encryption keys raise JS exceptions.
|
|
197
|
+
Only generation is asynchronous; import/export/sign/encrypt/decrypt are
|
|
198
|
+
synchronous and should be used from worker threads for large workloads.
|
|
199
|
+
|
|
200
|
+
## Packaging And CI
|
|
201
|
+
|
|
202
|
+
From a successful Release build:
|
|
203
|
+
|
|
204
|
+
```sh
|
|
205
|
+
npm run package
|
|
206
|
+
npm run test:package
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`bundle` copies the addon into `prebuilds/<platform>-<arch>/`, gathers its runtime
|
|
210
|
+
dependencies, rewrites native loader paths, and copies dependency license
|
|
211
|
+
notices. macOS needs `dylibbundler` and Apple developer tools; Linux needs
|
|
212
|
+
`patchelf` and Debian/Ubuntu dependency copyright files. Windows uses the vcpkg
|
|
213
|
+
installed tree (`VCPKG_INSTALLED_DIR`, `VCPKG_DEFAULT_TRIPLET`) and MSVC tools.
|
|
214
|
+
`OPENDHT_LIBRARY_PATH` adds native library search directories when bundling.
|
|
215
|
+
An explicit addon path may be passed to `npm run bundle -- /absolute/addon.node`.
|
|
216
|
+
Bundling replaces only the current platform's generated prebuild directory.
|
|
217
|
+
|
|
218
|
+
`test:package` creates an npm archive, installs it in a temporary consumer with
|
|
219
|
+
install scripts disabled and no dev dependencies, forces prebuild-only loading,
|
|
220
|
+
and runs the full runtime suite. Pass an archive file or a directory containing
|
|
221
|
+
one archive to test it directly, for example `npm run test:package -- dist`.
|
|
222
|
+
Only runtime JS, declarations, the example's TypeScript source, documentation,
|
|
223
|
+
and prebuilds are included in the archive. `prepack` compiles the example, so
|
|
224
|
+
archive creation needs the development dependencies; archive installation does not.
|
|
225
|
+
|
|
226
|
+
The [Node.js package workflow](../.github/workflows/nodejs-package.yml) builds:
|
|
227
|
+
|
|
228
|
+
| Target | Build Baseline |
|
|
229
|
+
| --- | --- |
|
|
230
|
+
| Linux x64 | Ubuntu 24.04 / glibc 2.39 (Noble dependency image) |
|
|
231
|
+
| macOS ARM64 and x64 | macOS 15 |
|
|
232
|
+
| Windows x64 | Windows Server 2022 / MSVC |
|
|
233
|
+
|
|
234
|
+
CI combines platform prebuilds into one npm archive and verifies that archive on
|
|
235
|
+
Node 22, 24, and 26 on each platform, including Ubuntu 24.04 and 26.04. Branch
|
|
236
|
+
pushes, pull requests, and manual runs build and test without publishing.
|
|
237
|
+
musl/Alpine and other architectures are not currently built. Node-API stability
|
|
238
|
+
does not remove OS/libc or native dependency compatibility requirements. Review
|
|
239
|
+
the included dependency licenses and source-distribution obligations before
|
|
240
|
+
redistributing binaries.
|
|
241
|
+
|
|
242
|
+
Query filters, AbortSignal support, certificate trust-store validation, CA
|
|
243
|
+
issuance controls, and runtime proxy switching remain future work.
|
package/crypto.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export default function createCrypto(native) {
|
|
2
|
+
const handles = new WeakMap();
|
|
3
|
+
|
|
4
|
+
function handle(value, Type) {
|
|
5
|
+
if (!(value instanceof Type) || !handles.has(value)) throw new TypeError(`Expected ${Type.name}`);
|
|
6
|
+
return handles.get(value);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function wrap(Type, nativeHandle) {
|
|
10
|
+
const result = Object.create(Type.prototype);
|
|
11
|
+
handles.set(result, nativeHandle);
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function generate(kind, name, options = {}) {
|
|
16
|
+
if (!options || typeof options !== 'object' || Array.isArray(options)) throw new TypeError('Invalid crypto options');
|
|
17
|
+
return native.cryptoGenerate(kind, name, options.algorithm ?? 'ec', options.bits ?? 2048);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class PublicKey {
|
|
21
|
+
constructor(data) { handles.set(this, native.cryptoImport('public', data)); }
|
|
22
|
+
serialize() { return native.cryptoSerialize(handle(this, PublicKey)); }
|
|
23
|
+
getId() { return native.cryptoId(handle(this, PublicKey), false); }
|
|
24
|
+
getLongId() { return native.cryptoId(handle(this, PublicKey), true); }
|
|
25
|
+
checkSignature(data, signature) { return native.cryptoVerify(handle(this, PublicKey), data, signature); }
|
|
26
|
+
encrypt(data) { return native.cryptoEncrypt(handle(this, PublicKey), data); }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class PrivateKey {
|
|
30
|
+
constructor(data, password = '') { handles.set(this, native.cryptoImport('private', data, password)); }
|
|
31
|
+
static generate(options) { return generate('private', '', options).then(value => wrap(PrivateKey, value)); }
|
|
32
|
+
serialize(password = '') { return native.cryptoSerialize(handle(this, PrivateKey), password); }
|
|
33
|
+
getPublicKey() { return wrap(PublicKey, native.cryptoPublicKey(handle(this, PrivateKey))); }
|
|
34
|
+
sign(data) { return native.cryptoSign(handle(this, PrivateKey), data); }
|
|
35
|
+
decrypt(data) { return native.cryptoDecrypt(handle(this, PrivateKey), data); }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class Certificate {
|
|
39
|
+
constructor(data) { handles.set(this, native.cryptoImport('certificate', data)); }
|
|
40
|
+
serialize() { return native.cryptoSerialize(handle(this, Certificate)); }
|
|
41
|
+
getPublicKey() { return wrap(PublicKey, native.cryptoPublicKey(handle(this, Certificate))); }
|
|
42
|
+
getId() { return native.cryptoId(handle(this, Certificate), false); }
|
|
43
|
+
getLongId() { return native.cryptoId(handle(this, Certificate), true); }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class Identity {
|
|
47
|
+
constructor(privateKey, certificate) {
|
|
48
|
+
handles.set(this, native.cryptoIdentity(handle(privateKey, PrivateKey), handle(certificate, Certificate)));
|
|
49
|
+
}
|
|
50
|
+
static generate(name = 'dhtnode', options) { return generate('identity', name, options).then(value => wrap(Identity, value)); }
|
|
51
|
+
get privateKey() { return wrap(PrivateKey, native.cryptoIdentityPart(handle(this, Identity), true)); }
|
|
52
|
+
get certificate() { return wrap(Certificate, native.cryptoIdentityPart(handle(this, Identity), false)); }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { PublicKey, PrivateKey, Certificate, Identity, handle };
|
|
56
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { clearLine, createInterface, cursorTo } from 'node:readline';
|
|
3
|
+
import { parseArgs } from 'node:util';
|
|
4
|
+
const commands = 'get KEY | put KEY TEXT | sign KEY TEXT | listen KEY | cancel KEY | help | quit';
|
|
5
|
+
function portNumber(value, name, minimum = 0) {
|
|
6
|
+
const port = Number(value);
|
|
7
|
+
if (!Number.isInteger(port) || port < minimum || port > 65535) {
|
|
8
|
+
throw new Error(`${name} must be an integer between ${minimum} and 65535`);
|
|
9
|
+
}
|
|
10
|
+
return port;
|
|
11
|
+
}
|
|
12
|
+
function errorMessage(error) {
|
|
13
|
+
return error instanceof Error ? error.message : String(error);
|
|
14
|
+
}
|
|
15
|
+
function report(error) {
|
|
16
|
+
console.error(errorMessage(error));
|
|
17
|
+
process.exitCode = 1;
|
|
18
|
+
}
|
|
19
|
+
async function main() {
|
|
20
|
+
const { values } = parseArgs({
|
|
21
|
+
options: {
|
|
22
|
+
help: { type: 'boolean', short: 'h' },
|
|
23
|
+
port: { type: 'string', short: 'p', default: '4222' },
|
|
24
|
+
ipv4: { type: 'string', default: '0.0.0.0' },
|
|
25
|
+
ipv6: { type: 'string', default: '::' },
|
|
26
|
+
bootstrap: { type: 'string', short: 'b' },
|
|
27
|
+
'bootstrap-port': { type: 'string', default: '4222' },
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
if (values.help) {
|
|
31
|
+
console.log(`Usage: npm run dhtnode -- [options]
|
|
32
|
+
|
|
33
|
+
-p, --port PORT Local UDP port (default: 4222; 0 selects a free port)
|
|
34
|
+
-b, --bootstrap HOST Optional bootstrap hostname or IP address
|
|
35
|
+
--bootstrap-port PORT Bootstrap UDP port (default: 4222)
|
|
36
|
+
--ipv4 ADDRESS IPv4 bind address (default: 0.0.0.0)
|
|
37
|
+
--ipv6 ADDRESS IPv6 bind address (default: ::)
|
|
38
|
+
-h, --help Show help
|
|
39
|
+
|
|
40
|
+
Commands: ${commands}
|
|
41
|
+
Keys are text labels hashed with SHA-1. Values are UTF-8 text.`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const port = portNumber(values.port, 'port');
|
|
45
|
+
const bootstrapPort = portNumber(values['bootstrap-port'], 'bootstrap-port', 1);
|
|
46
|
+
const { DhtRunner, Identity, hash } = await import('../index.js');
|
|
47
|
+
const identity = await Identity.generate('dhtnode.ts');
|
|
48
|
+
const runner = new DhtRunner();
|
|
49
|
+
const listeners = new Map();
|
|
50
|
+
const pending = new Set();
|
|
51
|
+
let lines;
|
|
52
|
+
let closing;
|
|
53
|
+
function close() {
|
|
54
|
+
if (!closing) {
|
|
55
|
+
for (const listener of listeners.values())
|
|
56
|
+
listener.cancel();
|
|
57
|
+
listeners.clear();
|
|
58
|
+
lines?.close();
|
|
59
|
+
closing = runner.close();
|
|
60
|
+
}
|
|
61
|
+
return closing;
|
|
62
|
+
}
|
|
63
|
+
function print(message) {
|
|
64
|
+
if (process.stdout.isTTY && lines) {
|
|
65
|
+
clearLine(process.stdout, 0);
|
|
66
|
+
cursorTo(process.stdout, 0);
|
|
67
|
+
}
|
|
68
|
+
console.log(message);
|
|
69
|
+
if (!closing && process.stdin.isTTY)
|
|
70
|
+
lines?.prompt(true);
|
|
71
|
+
}
|
|
72
|
+
const stop = () => { close().catch(report); };
|
|
73
|
+
const printValue = (label, value) => {
|
|
74
|
+
print(`${label} ${value.id}: ${JSON.stringify(value.data.toString('utf8'))}${value.isSigned ? ' [signed]' : ''}`);
|
|
75
|
+
};
|
|
76
|
+
const printStatus = (status) => {
|
|
77
|
+
print(`Connectivity: IPv4 ${status.ipv4}, IPv6 ${status.ipv6}`);
|
|
78
|
+
};
|
|
79
|
+
async function execute(line) {
|
|
80
|
+
const [, command, key, data] = line.trim().match(/^(\S+)(?:\s+(\S+))?(?:\s+(.+))?$/) || [];
|
|
81
|
+
if (!command)
|
|
82
|
+
return;
|
|
83
|
+
if (command === 'help') {
|
|
84
|
+
print(commands);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (!key)
|
|
88
|
+
throw new Error('Expected a key. Type help for commands.');
|
|
89
|
+
const location = hash(key);
|
|
90
|
+
switch (command) {
|
|
91
|
+
case 'get': {
|
|
92
|
+
const found = await runner.getAll(location);
|
|
93
|
+
if (closing)
|
|
94
|
+
return;
|
|
95
|
+
for (const value of found)
|
|
96
|
+
printValue(key, value);
|
|
97
|
+
if (!found.length)
|
|
98
|
+
print(`No values for ${key}`);
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
case 'put':
|
|
102
|
+
case 'sign': {
|
|
103
|
+
if (data === undefined)
|
|
104
|
+
throw new Error(`Usage: ${command} KEY TEXT`);
|
|
105
|
+
const payload = Buffer.from(data, 'utf8');
|
|
106
|
+
const ok = command === 'sign' ? await runner.putSigned(location, payload)
|
|
107
|
+
: await runner.put(location, payload);
|
|
108
|
+
if (closing)
|
|
109
|
+
return;
|
|
110
|
+
if (!ok)
|
|
111
|
+
throw new Error(`Put failed for ${key}`);
|
|
112
|
+
print(`Stored ${key}`);
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case 'listen':
|
|
116
|
+
if (!listeners.has(key)) {
|
|
117
|
+
listeners.set(key, runner.listen(location, (value, expired) => {
|
|
118
|
+
printValue(`${expired ? 'Expired' : 'Value'} ${key}`, value);
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
print(`Listening ${key}`);
|
|
122
|
+
break;
|
|
123
|
+
case 'cancel':
|
|
124
|
+
listeners.get(key)?.cancel();
|
|
125
|
+
listeners.delete(key);
|
|
126
|
+
print(`Cancelled ${key}`);
|
|
127
|
+
break;
|
|
128
|
+
default:
|
|
129
|
+
throw new Error('Unknown command. Type help for commands.');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
runner.on('error', error => { report(error); stop(); });
|
|
133
|
+
runner.on('statusChanged', printStatus);
|
|
134
|
+
process.on('SIGINT', stop);
|
|
135
|
+
process.on('SIGTERM', stop);
|
|
136
|
+
try {
|
|
137
|
+
runner.run({ port, ipv4: values.ipv4, ipv6: values.ipv6, identity });
|
|
138
|
+
if (values.bootstrap)
|
|
139
|
+
runner.bootstrap(values.bootstrap, bootstrapPort);
|
|
140
|
+
lines = createInterface({ input: process.stdin, output: process.stdout });
|
|
141
|
+
lines.on('SIGINT', stop);
|
|
142
|
+
lines.setPrompt('dht> ');
|
|
143
|
+
print(`Node ${runner.getNodeId()} listening on UDP ${runner.getBoundPort()}`);
|
|
144
|
+
printStatus(runner.getStatus());
|
|
145
|
+
if (process.stdin.isTTY)
|
|
146
|
+
print(commands);
|
|
147
|
+
for await (const line of lines) {
|
|
148
|
+
if (closing)
|
|
149
|
+
break;
|
|
150
|
+
if (/^(quit|exit)(?:\s|$)/.test(line.trim())) {
|
|
151
|
+
stop();
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
const operation = execute(line).catch(error => {
|
|
155
|
+
if (!closing)
|
|
156
|
+
console.error(errorMessage(error));
|
|
157
|
+
}).finally(() => { pending.delete(operation); });
|
|
158
|
+
pending.add(operation);
|
|
159
|
+
if (process.stdin.isTTY)
|
|
160
|
+
lines.prompt();
|
|
161
|
+
}
|
|
162
|
+
if (!closing)
|
|
163
|
+
await Promise.all(pending);
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
try {
|
|
167
|
+
await close();
|
|
168
|
+
await Promise.all(pending);
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
process.removeListener('SIGINT', stop);
|
|
172
|
+
process.removeListener('SIGTERM', stop);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
main().catch(report);
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { clearLine, createInterface, cursorTo, type Interface } from 'node:readline';
|
|
4
|
+
import { parseArgs } from 'node:util';
|
|
5
|
+
import type { ConnectivityStatus, Listener, Value } from '../index.js';
|
|
6
|
+
|
|
7
|
+
const commands = 'get KEY | put KEY TEXT | sign KEY TEXT | listen KEY | cancel KEY | help | quit';
|
|
8
|
+
|
|
9
|
+
function portNumber(value: string, name: string, minimum = 0): number {
|
|
10
|
+
const port = Number(value);
|
|
11
|
+
if (!Number.isInteger(port) || port < minimum || port > 65535) {
|
|
12
|
+
throw new Error(`${name} must be an integer between ${minimum} and 65535`);
|
|
13
|
+
}
|
|
14
|
+
return port;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function errorMessage(error: unknown): string {
|
|
18
|
+
return error instanceof Error ? error.message : String(error);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function report(error: unknown): void {
|
|
22
|
+
console.error(errorMessage(error));
|
|
23
|
+
process.exitCode = 1;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function main(): Promise<void> {
|
|
27
|
+
const { values } = parseArgs({
|
|
28
|
+
options: {
|
|
29
|
+
help: { type: 'boolean', short: 'h' },
|
|
30
|
+
port: { type: 'string', short: 'p', default: '4222' },
|
|
31
|
+
ipv4: { type: 'string', default: '0.0.0.0' },
|
|
32
|
+
ipv6: { type: 'string', default: '::' },
|
|
33
|
+
bootstrap: { type: 'string', short: 'b' },
|
|
34
|
+
'bootstrap-port': { type: 'string', default: '4222' },
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
if (values.help) {
|
|
38
|
+
console.log(`Usage: npm run dhtnode -- [options]
|
|
39
|
+
|
|
40
|
+
-p, --port PORT Local UDP port (default: 4222; 0 selects a free port)
|
|
41
|
+
-b, --bootstrap HOST Optional bootstrap hostname or IP address
|
|
42
|
+
--bootstrap-port PORT Bootstrap UDP port (default: 4222)
|
|
43
|
+
--ipv4 ADDRESS IPv4 bind address (default: 0.0.0.0)
|
|
44
|
+
--ipv6 ADDRESS IPv6 bind address (default: ::)
|
|
45
|
+
-h, --help Show help
|
|
46
|
+
|
|
47
|
+
Commands: ${commands}
|
|
48
|
+
Keys are text labels hashed with SHA-1. Values are UTF-8 text.`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const port = portNumber(values.port, 'port');
|
|
52
|
+
const bootstrapPort = portNumber(values['bootstrap-port'], 'bootstrap-port', 1);
|
|
53
|
+
const { DhtRunner, Identity, hash } = await import('../index.js');
|
|
54
|
+
const identity = await Identity.generate('dhtnode.ts');
|
|
55
|
+
const runner = new DhtRunner();
|
|
56
|
+
const listeners = new Map<string, Listener>();
|
|
57
|
+
const pending = new Set<Promise<void>>();
|
|
58
|
+
let lines: Interface | undefined;
|
|
59
|
+
let closing: Promise<void> | undefined;
|
|
60
|
+
|
|
61
|
+
function close(): Promise<void> {
|
|
62
|
+
if (!closing) {
|
|
63
|
+
for (const listener of listeners.values()) listener.cancel();
|
|
64
|
+
listeners.clear();
|
|
65
|
+
lines?.close();
|
|
66
|
+
closing = runner.close();
|
|
67
|
+
}
|
|
68
|
+
return closing;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function print(message: string): void {
|
|
72
|
+
if (process.stdout.isTTY && lines) {
|
|
73
|
+
clearLine(process.stdout, 0);
|
|
74
|
+
cursorTo(process.stdout, 0);
|
|
75
|
+
}
|
|
76
|
+
console.log(message);
|
|
77
|
+
if (!closing && process.stdin.isTTY) lines?.prompt(true);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const stop = (): void => { close().catch(report); };
|
|
81
|
+
const printValue = (label: string, value: Value): void => {
|
|
82
|
+
print(`${label} ${value.id}: ${JSON.stringify(value.data.toString('utf8'))}${value.isSigned ? ' [signed]' : ''}`);
|
|
83
|
+
};
|
|
84
|
+
const printStatus = (status: ConnectivityStatus): void => {
|
|
85
|
+
print(`Connectivity: IPv4 ${status.ipv4}, IPv6 ${status.ipv6}`);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
async function execute(line: string): Promise<void> {
|
|
89
|
+
const [, command, key, data] = line.trim().match(/^(\S+)(?:\s+(\S+))?(?:\s+(.+))?$/) || [];
|
|
90
|
+
if (!command) return;
|
|
91
|
+
if (command === 'help') {
|
|
92
|
+
print(commands);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (!key) throw new Error('Expected a key. Type help for commands.');
|
|
96
|
+
const location = hash(key);
|
|
97
|
+
switch (command) {
|
|
98
|
+
case 'get': {
|
|
99
|
+
const found = await runner.getAll(location);
|
|
100
|
+
if (closing) return;
|
|
101
|
+
for (const value of found) printValue(key, value);
|
|
102
|
+
if (!found.length) print(`No values for ${key}`);
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
case 'put':
|
|
106
|
+
case 'sign': {
|
|
107
|
+
if (data === undefined) throw new Error(`Usage: ${command} KEY TEXT`);
|
|
108
|
+
const payload = Buffer.from(data, 'utf8');
|
|
109
|
+
const ok = command === 'sign' ? await runner.putSigned(location, payload)
|
|
110
|
+
: await runner.put(location, payload);
|
|
111
|
+
if (closing) return;
|
|
112
|
+
if (!ok) throw new Error(`Put failed for ${key}`);
|
|
113
|
+
print(`Stored ${key}`);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case 'listen':
|
|
117
|
+
if (!listeners.has(key)) {
|
|
118
|
+
listeners.set(key, runner.listen(location, (value, expired) => {
|
|
119
|
+
printValue(`${expired ? 'Expired' : 'Value'} ${key}`, value);
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
print(`Listening ${key}`);
|
|
123
|
+
break;
|
|
124
|
+
case 'cancel':
|
|
125
|
+
listeners.get(key)?.cancel();
|
|
126
|
+
listeners.delete(key);
|
|
127
|
+
print(`Cancelled ${key}`);
|
|
128
|
+
break;
|
|
129
|
+
default:
|
|
130
|
+
throw new Error('Unknown command. Type help for commands.');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
runner.on('error', error => { report(error); stop(); });
|
|
135
|
+
runner.on('statusChanged', printStatus);
|
|
136
|
+
process.on('SIGINT', stop);
|
|
137
|
+
process.on('SIGTERM', stop);
|
|
138
|
+
try {
|
|
139
|
+
runner.run({ port, ipv4: values.ipv4, ipv6: values.ipv6, identity });
|
|
140
|
+
if (values.bootstrap) runner.bootstrap(values.bootstrap, bootstrapPort);
|
|
141
|
+
lines = createInterface({ input: process.stdin, output: process.stdout });
|
|
142
|
+
lines.on('SIGINT', stop);
|
|
143
|
+
lines.setPrompt('dht> ');
|
|
144
|
+
print(`Node ${runner.getNodeId()} listening on UDP ${runner.getBoundPort()}`);
|
|
145
|
+
printStatus(runner.getStatus());
|
|
146
|
+
if (process.stdin.isTTY) print(commands);
|
|
147
|
+
for await (const line of lines) {
|
|
148
|
+
if (closing) break;
|
|
149
|
+
if (/^(quit|exit)(?:\s|$)/.test(line.trim())) {
|
|
150
|
+
stop();
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
const operation = execute(line).catch(error => {
|
|
154
|
+
if (!closing) console.error(errorMessage(error));
|
|
155
|
+
}).finally(() => { pending.delete(operation); });
|
|
156
|
+
pending.add(operation);
|
|
157
|
+
if (process.stdin.isTTY) lines.prompt();
|
|
158
|
+
}
|
|
159
|
+
if (!closing) await Promise.all(pending);
|
|
160
|
+
} finally {
|
|
161
|
+
try {
|
|
162
|
+
await close();
|
|
163
|
+
await Promise.all(pending);
|
|
164
|
+
} finally {
|
|
165
|
+
process.removeListener('SIGINT', stop);
|
|
166
|
+
process.removeListener('SIGTERM', stop);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
main().catch(report);
|