blind-peer 3.4.0 → 3.6.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/index.js +188 -4
- package/lib/top-k.js +94 -0
- package/package.json +20 -7
package/index.js
CHANGED
|
@@ -12,14 +12,25 @@ const safetyCatch = require('safety-catch')
|
|
|
12
12
|
const Wakeup = require('protomux-wakeup')
|
|
13
13
|
const ScopeLock = require('scope-lock')
|
|
14
14
|
const IdEnc = require('hypercore-id-encoding')
|
|
15
|
+
const ProtomuxRpcClientPool = require('protomux-rpc-client-pool')
|
|
16
|
+
const ProtomuxRpcClient = require('protomux-rpc-client')
|
|
17
|
+
const rrp = require('resolve-reject-promise')
|
|
18
|
+
const IpBanList = require('ip-ban-list')
|
|
19
|
+
const {
|
|
20
|
+
ADMIN_CHANNEL_ID,
|
|
21
|
+
AdminQueryTopKEncoding,
|
|
22
|
+
AddCoreEncoding,
|
|
23
|
+
DeleteCoreEncoding,
|
|
24
|
+
RouterResolvePeersRequest,
|
|
25
|
+
RouterResolvePeersResponse
|
|
26
|
+
} = require('blind-peer-encodings')
|
|
15
27
|
|
|
16
28
|
const BlindPeerDB = require('./lib/db.js')
|
|
29
|
+
const TopKWindow = require('./lib/top-k.js')
|
|
17
30
|
|
|
18
31
|
// Enable Small wants in Hypercore. Must be before anywhere that uses Hypercore
|
|
19
32
|
Hypercore.enable(Hypercore.SMALL_WANTS)
|
|
20
33
|
|
|
21
|
-
const { AddCoreEncoding, DeleteCoreEncoding } = require('blind-peer-encodings')
|
|
22
|
-
|
|
23
34
|
class CoreTracker {
|
|
24
35
|
constructor(blindPeer, core) {
|
|
25
36
|
this.blindPeer = blindPeer
|
|
@@ -197,10 +208,16 @@ class BlindPeer extends ReadyResource {
|
|
|
197
208
|
maxBytes = 100_000_000_000,
|
|
198
209
|
enableGc = true,
|
|
199
210
|
trustedPubKeys,
|
|
211
|
+
routerKey,
|
|
212
|
+
routerPoolOpts,
|
|
213
|
+
ipBanListKeys = [],
|
|
214
|
+
banTimeout = 16_000,
|
|
200
215
|
port,
|
|
201
216
|
announcingInterval = 100,
|
|
202
217
|
wakeupGcTickTime = null,
|
|
203
|
-
replicationLagThreshold = 100
|
|
218
|
+
replicationLagThreshold = 100,
|
|
219
|
+
topK = {},
|
|
220
|
+
adminRouter = null
|
|
204
221
|
} = {}
|
|
205
222
|
) {
|
|
206
223
|
super()
|
|
@@ -208,6 +225,10 @@ class BlindPeer extends ReadyResource {
|
|
|
208
225
|
this.rocks = typeof rocks === 'string' ? new RocksDB(rocks) : rocks
|
|
209
226
|
this.store = store || new Corestore(this.rocks, { active: false })
|
|
210
227
|
this.swarm = swarm || null
|
|
228
|
+
const ipBanNs = this.store.namespace('ip-ban-lists')
|
|
229
|
+
this.ipBanLists = ipBanListKeys.map((key) => new IpBanList(ipBanNs, { key }))
|
|
230
|
+
this.banTimeout = banTimeout
|
|
231
|
+
|
|
211
232
|
this._port = port || 0
|
|
212
233
|
this.announcingInterval = announcingInterval
|
|
213
234
|
this.trustedPubKeys = new Set()
|
|
@@ -227,6 +248,11 @@ class BlindPeer extends ReadyResource {
|
|
|
227
248
|
this.announcedCores = new Map()
|
|
228
249
|
this.replicationLagThreshold = replicationLagThreshold
|
|
229
250
|
|
|
251
|
+
this.routerKey = routerKey || null
|
|
252
|
+
this.routerPoolOpts = routerPoolOpts || {}
|
|
253
|
+
this.routerPool = null
|
|
254
|
+
this.adminRouter = adminRouter
|
|
255
|
+
|
|
230
256
|
this.stats = {
|
|
231
257
|
bytesGcd: 0,
|
|
232
258
|
coresAdded: 0,
|
|
@@ -236,6 +262,35 @@ class BlindPeer extends ReadyResource {
|
|
|
236
262
|
muxerPaired: 0,
|
|
237
263
|
muxerErrors: 0
|
|
238
264
|
}
|
|
265
|
+
|
|
266
|
+
const {
|
|
267
|
+
bucketCount = 6,
|
|
268
|
+
bucketTime = 10_000,
|
|
269
|
+
k = 5,
|
|
270
|
+
peerThreshold = 100,
|
|
271
|
+
referrerThreshold = 100
|
|
272
|
+
} = topK
|
|
273
|
+
|
|
274
|
+
this.topKByPeer = new TopKWindow(bucketCount, bucketTime, k, peerThreshold)
|
|
275
|
+
this.topKByReferrer = new TopKWindow(bucketCount, bucketTime, k, referrerThreshold)
|
|
276
|
+
this.topKByIp = new TopKWindow(bucketCount, bucketTime, k, null) // we do not want to expose the ip spike
|
|
277
|
+
|
|
278
|
+
if (this.adminRouter) {
|
|
279
|
+
this.adminRouter.use({
|
|
280
|
+
onrequest: (ctx, next) => {
|
|
281
|
+
if (!this._isTrustedPeer(ctx.connection.remotePublicKey)) {
|
|
282
|
+
throw new Error('Only trusted peers can query top-k')
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return next()
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
this.adminRouter.method(
|
|
289
|
+
'query-top-k',
|
|
290
|
+
AdminQueryTopKEncoding,
|
|
291
|
+
this._onadminquerytopk.bind(this)
|
|
292
|
+
)
|
|
293
|
+
}
|
|
239
294
|
}
|
|
240
295
|
|
|
241
296
|
get encryptionPublicKey() {
|
|
@@ -262,6 +317,15 @@ class BlindPeer extends ReadyResource {
|
|
|
262
317
|
return this.trustedPubKeys.has(IdEnc.normalize(key))
|
|
263
318
|
}
|
|
264
319
|
|
|
320
|
+
_onadminquerytopk() {
|
|
321
|
+
return {
|
|
322
|
+
version: 1,
|
|
323
|
+
ip: this.topKByIp.topK,
|
|
324
|
+
referrer: this.topKByReferrer.topK,
|
|
325
|
+
peerPublicKey: this.topKByPeer.topK
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
265
329
|
async _open() {
|
|
266
330
|
await this.store.ready()
|
|
267
331
|
|
|
@@ -285,6 +349,23 @@ class BlindPeer extends ReadyResource {
|
|
|
285
349
|
}
|
|
286
350
|
this.swarm.on('connection', this._onconnection.bind(this))
|
|
287
351
|
|
|
352
|
+
await Promise.all(
|
|
353
|
+
this.ipBanLists.map(async (banIpList) => {
|
|
354
|
+
await banIpList.ready()
|
|
355
|
+
this.swarm.join(banIpList.discoveryKey, { server: false, client: true })
|
|
356
|
+
})
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
if (this.routerKey) {
|
|
360
|
+
const rpcClient = new ProtomuxRpcClient(this.swarm.dht)
|
|
361
|
+
this.routerPool = new ProtomuxRpcClientPool([this.routerKey], rpcClient, this.routerPoolOpts)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
await this.topKByPeer.ready()
|
|
365
|
+
await this.topKByReferrer.ready()
|
|
366
|
+
await this.topKByIp.ready()
|
|
367
|
+
if (this.adminRouter) await this.adminRouter.ready()
|
|
368
|
+
|
|
288
369
|
this._announceCores().catch(safetyCatch) // announcing cores asynchronously
|
|
289
370
|
this.flushInterval = setInterval(this.flush.bind(this), 10_000)
|
|
290
371
|
}
|
|
@@ -432,6 +513,7 @@ class BlindPeer extends ReadyResource {
|
|
|
432
513
|
|
|
433
514
|
rpc.respond('add-core', AddCoreEncoding, this._onaddcore.bind(this, conn))
|
|
434
515
|
rpc.respond('delete-core', DeleteCoreEncoding, this._ondeletecore.bind(this, conn))
|
|
516
|
+
if (this.adminRouter) this.adminRouter.handleConnection(conn, ADMIN_CHANNEL_ID)
|
|
435
517
|
|
|
436
518
|
const self = this
|
|
437
519
|
BlindPeerMuxer.pair(conn, function () {
|
|
@@ -451,6 +533,36 @@ class BlindPeer extends ReadyResource {
|
|
|
451
533
|
})
|
|
452
534
|
}
|
|
453
535
|
|
|
536
|
+
_isBlocked(conn) {
|
|
537
|
+
// current behavior:
|
|
538
|
+
// as we do simple check ban
|
|
539
|
+
// bans can run in parallel across lists, but only the original banner can unban
|
|
540
|
+
for (const ipBanList of this.ipBanLists) {
|
|
541
|
+
if (ipBanList.isBanned(conn.rawStream.remoteHost)) {
|
|
542
|
+
return true
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return false
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
async _timeoutThenThrow() {
|
|
549
|
+
const { promise, resolve } = rrp()
|
|
550
|
+
|
|
551
|
+
const done = () => {
|
|
552
|
+
clearTimeout(timer)
|
|
553
|
+
this.off('close', done)
|
|
554
|
+
resolve()
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const timer = setTimeout(done, this.banTimeout)
|
|
558
|
+
timer.unref()
|
|
559
|
+
|
|
560
|
+
this.on('close', done)
|
|
561
|
+
|
|
562
|
+
await promise
|
|
563
|
+
throw new Error('Timed out')
|
|
564
|
+
}
|
|
565
|
+
|
|
454
566
|
async _activateCore(stream, record) {
|
|
455
567
|
this.stats.activations++
|
|
456
568
|
|
|
@@ -473,6 +585,23 @@ class BlindPeer extends ReadyResource {
|
|
|
473
585
|
stream.on('close', () => core.close().catch(safetyCatch))
|
|
474
586
|
}
|
|
475
587
|
|
|
588
|
+
async _resolvePeers(key) {
|
|
589
|
+
if (!this.routerPool) return
|
|
590
|
+
|
|
591
|
+
const result = await this.routerPool.makeRequest(
|
|
592
|
+
'resolve-peers',
|
|
593
|
+
{ key },
|
|
594
|
+
{
|
|
595
|
+
requestEncoding: RouterResolvePeersRequest,
|
|
596
|
+
responseEncoding: RouterResolvePeersResponse
|
|
597
|
+
}
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
this.emit('resolve-peers', { key, result })
|
|
601
|
+
|
|
602
|
+
return result
|
|
603
|
+
}
|
|
604
|
+
|
|
476
605
|
async _announceCores() {
|
|
477
606
|
for await (const record of this.db.createAnnouncingCoresStream()) {
|
|
478
607
|
if (this.closing) return
|
|
@@ -534,6 +663,10 @@ class BlindPeer extends ReadyResource {
|
|
|
534
663
|
|
|
535
664
|
async _onaddcore(stream, record) {
|
|
536
665
|
if (!this.opened) await this.ready()
|
|
666
|
+
if (this._isBlocked(stream)) {
|
|
667
|
+
this.emit('connection-banned', stream)
|
|
668
|
+
await this._timeoutThenThrow()
|
|
669
|
+
}
|
|
537
670
|
|
|
538
671
|
record.priority = Math.min(record.priority, 1) // 2 is reserved for trusted peers
|
|
539
672
|
if (record.announce !== false && !this._isTrustedPeer(stream.remotePublicKey)) {
|
|
@@ -574,9 +707,20 @@ class BlindPeer extends ReadyResource {
|
|
|
574
707
|
}
|
|
575
708
|
|
|
576
709
|
async _onaddcores(stream, request) {
|
|
710
|
+
if (this._isBlocked(stream)) {
|
|
711
|
+
this.emit('connection-banned', stream)
|
|
712
|
+
throw new Error('Timed out')
|
|
713
|
+
}
|
|
577
714
|
this.stats.addCoresRx++
|
|
578
|
-
|
|
715
|
+
|
|
579
716
|
const { cores, referrer } = request
|
|
717
|
+
if (referrer) {
|
|
718
|
+
this.topKByReferrer.hit(IdEnc.normalize(referrer))
|
|
719
|
+
}
|
|
720
|
+
this.topKByPeer.hit(IdEnc.normalize(stream.remotePublicKey))
|
|
721
|
+
this.topKByIp.hit(stream.rawStream.remoteHost)
|
|
722
|
+
|
|
723
|
+
const priority = Math.min(request.priority, 1) // 2 is reserved for trusted peers
|
|
580
724
|
|
|
581
725
|
if (request.announce !== false && !this._isTrustedPeer(stream.remotePublicKey)) {
|
|
582
726
|
// Note: we can't use the original downgrade-announce event because that assumes a 'record' object
|
|
@@ -650,6 +794,13 @@ class BlindPeer extends ReadyResource {
|
|
|
650
794
|
const discoveryKey = core.discoveryKey
|
|
651
795
|
await core.close()
|
|
652
796
|
await this._onwakeup(discoveryKey, muxer)
|
|
797
|
+
|
|
798
|
+
// TODO: will process the result in V2
|
|
799
|
+
// TODO: handle no referrer
|
|
800
|
+
this._resolvePeers(referrer).catch((err) => {
|
|
801
|
+
safetyCatch(err)
|
|
802
|
+
this.emit('resolve-peers-error', { key: referrer, error: err })
|
|
803
|
+
})
|
|
653
804
|
}
|
|
654
805
|
|
|
655
806
|
for (const r of recordsToAdd) this.emit('add-new-core', r, true, stream)
|
|
@@ -668,6 +819,10 @@ class BlindPeer extends ReadyResource {
|
|
|
668
819
|
}
|
|
669
820
|
|
|
670
821
|
async _ondeletecore(stream, { key }) {
|
|
822
|
+
if (this._isBlocked(stream)) {
|
|
823
|
+
this.emit('connection-banned', stream)
|
|
824
|
+
await this._timeoutThenThrow()
|
|
825
|
+
}
|
|
671
826
|
if (!this._isTrustedPeer(stream.remotePublicKey)) {
|
|
672
827
|
this.emit('delete-blocked', stream, { key })
|
|
673
828
|
throw new Error('Only trusted peers can delete cores')
|
|
@@ -713,7 +868,15 @@ class BlindPeer extends ReadyResource {
|
|
|
713
868
|
}
|
|
714
869
|
|
|
715
870
|
async _close() {
|
|
871
|
+
if (this.routerPool) {
|
|
872
|
+
await this.routerPool.destroy()
|
|
873
|
+
await this.routerPool.statelessRpc.close()
|
|
874
|
+
}
|
|
875
|
+
if (this.adminRouter) await this.adminRouter.close()
|
|
716
876
|
clearInterval(this.flushInterval)
|
|
877
|
+
await this.topKByPeer.close()
|
|
878
|
+
await this.topKByReferrer.close()
|
|
879
|
+
await this.topKByIp.close()
|
|
717
880
|
if (this.ownsWakeup) this.wakeup.destroy()
|
|
718
881
|
if (this.ownsSwarm) await this.swarm.destroy()
|
|
719
882
|
await this.flush()
|
|
@@ -822,6 +985,27 @@ class BlindPeer extends ReadyResource {
|
|
|
822
985
|
this.set(self.stats.addCoresRx)
|
|
823
986
|
}
|
|
824
987
|
})
|
|
988
|
+
new promClient.Gauge({
|
|
989
|
+
name: 'blind_peer_add_cores_top5_by_remote_key',
|
|
990
|
+
help: 'The total number of requests from the top 5 peers in the last minute',
|
|
991
|
+
collect() {
|
|
992
|
+
this.set(self.topKByPeer.topKSum())
|
|
993
|
+
}
|
|
994
|
+
})
|
|
995
|
+
new promClient.Gauge({
|
|
996
|
+
name: 'blind_peer_add_cores_top5_by_referrer',
|
|
997
|
+
help: 'The total number of requests from the top 5 referrers in the last minute',
|
|
998
|
+
collect() {
|
|
999
|
+
this.set(self.topKByReferrer.topKSum())
|
|
1000
|
+
}
|
|
1001
|
+
})
|
|
1002
|
+
new promClient.Gauge({
|
|
1003
|
+
name: 'blind_peer_add_cores_top5_by_remote_ip',
|
|
1004
|
+
help: 'The total number of requests from the top 5 remote IPs in the last minute',
|
|
1005
|
+
collect() {
|
|
1006
|
+
this.set(self.topKByIp.topKSum())
|
|
1007
|
+
}
|
|
1008
|
+
})
|
|
825
1009
|
if (self.rocks.stats) {
|
|
826
1010
|
new promClient.Gauge({
|
|
827
1011
|
// eslint-disable-line no-new
|
package/lib/top-k.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const ReadyResource = require('ready-resource')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tracks the most frequent keys within a rolling time window.
|
|
5
|
+
*/
|
|
6
|
+
class TopKWindow extends ReadyResource {
|
|
7
|
+
/**
|
|
8
|
+
* @param {number} [bucketCount=6]
|
|
9
|
+
* @param {number} [bucketTime=10_000]
|
|
10
|
+
* @param {number} [k=5]
|
|
11
|
+
* @param {number | null} [spikeThreshold=null]
|
|
12
|
+
*/
|
|
13
|
+
constructor(bucketCount = 6, bucketTime = 10_000, k = 5, spikeThreshold = null) {
|
|
14
|
+
super()
|
|
15
|
+
|
|
16
|
+
this.bucketCount = bucketCount
|
|
17
|
+
this.bucketTime = bucketTime
|
|
18
|
+
this.k = k
|
|
19
|
+
this.spikeThreshold = spikeThreshold
|
|
20
|
+
|
|
21
|
+
/** @type {Map<string, number>[]} */
|
|
22
|
+
this._buckets = Array.from({ length: bucketCount }, () => new Map())
|
|
23
|
+
this._index = 0
|
|
24
|
+
this.topK = []
|
|
25
|
+
this._timer = null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} key
|
|
30
|
+
*/
|
|
31
|
+
hit(key) {
|
|
32
|
+
const bucket = this._buckets[this._index]
|
|
33
|
+
bucket.set(key, (bucket.get(key) || 0) + 1)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @returns {number}
|
|
38
|
+
*/
|
|
39
|
+
topKSum() {
|
|
40
|
+
let sum = 0
|
|
41
|
+
for (const { count } of this.topK) {
|
|
42
|
+
sum += count
|
|
43
|
+
}
|
|
44
|
+
return sum
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_open() {
|
|
48
|
+
this._timer = setInterval(this._rotate.bind(this), this.bucketTime)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
_close() {
|
|
52
|
+
if (this._timer !== null) {
|
|
53
|
+
clearInterval(this._timer)
|
|
54
|
+
this._timer = null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.topK = []
|
|
58
|
+
for (const bucket of this._buckets) {
|
|
59
|
+
bucket.clear()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
_rotate() {
|
|
64
|
+
// compute the top-k and cache
|
|
65
|
+
const totals = new Map()
|
|
66
|
+
for (const bucket of this._buckets) {
|
|
67
|
+
for (const [key, count] of bucket) {
|
|
68
|
+
totals.set(key, (totals.get(key) || 0) + count)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.topK = [...totals.entries()]
|
|
73
|
+
.sort((a, b) => b[1] - a[1])
|
|
74
|
+
.slice(0, this.k)
|
|
75
|
+
.map(([key, count]) => ({ key, count }))
|
|
76
|
+
|
|
77
|
+
if (this.spikeThreshold !== null) {
|
|
78
|
+
// Deliberately emit only for cached top-k entries to keep spike volume bounded.
|
|
79
|
+
for (const { key, count } of this.topK) {
|
|
80
|
+
if (count >= this.spikeThreshold) {
|
|
81
|
+
this.emit('spike', key, count)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// rotate current bucket and clear the old bucket
|
|
87
|
+
this._index = (this._index + 1) % this.bucketCount
|
|
88
|
+
this._buckets[this._index].clear()
|
|
89
|
+
|
|
90
|
+
this.emit('rotated')
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = TopKWindow
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blind-peer",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "Blind peers help keep hypercores available",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"autobase": "^7.0.18",
|
|
8
8
|
"b4a": "^1.6.7",
|
|
9
|
-
"blind-peer-encodings": "^3.
|
|
9
|
+
"blind-peer-encodings": "^3.3.0",
|
|
10
10
|
"blind-peer-muxer": "^1.0.0",
|
|
11
11
|
"compact-encoding": "^2.16.0",
|
|
12
12
|
"corestore": "^7.4.4",
|
|
@@ -14,12 +14,17 @@
|
|
|
14
14
|
"hypercore-crypto": "^3.5.0",
|
|
15
15
|
"hypercore-id-encoding": "^1.3.0",
|
|
16
16
|
"hyperdb": "^5.0.0",
|
|
17
|
+
"hyperdht": "^6.29.0",
|
|
17
18
|
"hyperschema": "^1.10.3",
|
|
18
19
|
"hyperswarm": "^4.13.1",
|
|
20
|
+
"ip-ban-list": "^0.3.0",
|
|
19
21
|
"protomux-rpc": "^1.7.1",
|
|
22
|
+
"protomux-rpc-client": "^2.1.0",
|
|
23
|
+
"protomux-rpc-client-pool": "^2.1.0",
|
|
20
24
|
"protomux-wakeup": "^2.9.0",
|
|
21
25
|
"ready-resource": "^1.1.2",
|
|
22
26
|
"repl-swarm": "^2.3.0",
|
|
27
|
+
"resolve-reject-promise": "^1.0.3",
|
|
23
28
|
"rocksdb-native": "^3.1.6",
|
|
24
29
|
"safety-catch": "^1.0.2",
|
|
25
30
|
"scope-lock": "^1.2.4"
|
|
@@ -28,14 +33,17 @@
|
|
|
28
33
|
"bare-events": "^2.8.2",
|
|
29
34
|
"bare-process": "^4.2.2",
|
|
30
35
|
"bare-prom-client": "^15.1.3",
|
|
31
|
-
"blind-
|
|
36
|
+
"blind-peer-router": "^0.2.2",
|
|
37
|
+
"blind-peering": "^2.1.0",
|
|
32
38
|
"brittle": "^3.7.0",
|
|
33
39
|
"debounceify": "^1.1.0",
|
|
34
|
-
"
|
|
40
|
+
"graceful-goodbye": "^1.3.3",
|
|
41
|
+
"hyperdht-address": "^1.0.1",
|
|
42
|
+
"lunte": "^1.6.0",
|
|
35
43
|
"prettier": "^3.6.2",
|
|
36
44
|
"prettier-config-holepunch": "^2.0.0",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
45
|
+
"protomux-rpc-router": "^1.2.0",
|
|
46
|
+
"test-tmp": "^1.3.0"
|
|
39
47
|
},
|
|
40
48
|
"files": [
|
|
41
49
|
"index.js",
|
|
@@ -44,7 +52,8 @@
|
|
|
44
52
|
"scripts": {
|
|
45
53
|
"format": "prettier --write .",
|
|
46
54
|
"format:check": "prettier --check .",
|
|
47
|
-
"test": "npm run format:check && brittle test/*.js",
|
|
55
|
+
"test": "npm run format:check && lunte && brittle test/*.js",
|
|
56
|
+
"test:netns": "brittle test-netns/*.test.js",
|
|
48
57
|
"test:bare": "bare test/test.js"
|
|
49
58
|
},
|
|
50
59
|
"repository": {
|
|
@@ -61,6 +70,10 @@
|
|
|
61
70
|
"events": {
|
|
62
71
|
"bare": "bare-events",
|
|
63
72
|
"default": "events"
|
|
73
|
+
},
|
|
74
|
+
"prom-client": {
|
|
75
|
+
"bare": "bare-prom-client",
|
|
76
|
+
"default": "prom-client"
|
|
64
77
|
}
|
|
65
78
|
}
|
|
66
79
|
}
|