webpeerjs 0.0.1

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,1188 @@
1
+ import * as config from './config'
2
+ import {
3
+ mkErr,
4
+ PBPeer,
5
+ uint8ArrayToString,
6
+ uint8ArrayFromString,
7
+ //first,
8
+ Key,
9
+ msgIdFnStrictNoSign,
10
+ metrics } from './utils'
11
+ //import { createDelegatedRoutingV1HttpApiClient } from '@helia/delegated-routing-v1-http-api-client'
12
+ import { createLibp2p } from 'libp2p'
13
+ import { IDBDatastore } from 'datastore-idb'
14
+ import { webTransport } from '@libp2p/webtransport'
15
+ import { noise } from '@chainsafe/libp2p-noise'
16
+ import { yamux } from '@chainsafe/libp2p-yamux'
17
+ import { pubsubPeerDiscovery } from '@libp2p/pubsub-peer-discovery'
18
+ import { circuitRelayTransport } from '@libp2p/circuit-relay-v2'
19
+ import { gossipsub } from '@chainsafe/libp2p-gossipsub'
20
+ import { identify, identifyPush } from '@libp2p/identify'
21
+ import { multiaddr } from '@multiformats/multiaddr'
22
+ //import { peerIdFromString } from '@libp2p/peer-id'
23
+ import { kadDHT, removePrivateAddressesMapper } from '@libp2p/kad-dht'
24
+ import { simpleMetrics } from '@libp2p/simple-metrics'
25
+
26
+
27
+ class webpeerjs{
28
+
29
+ //libp2p instance
30
+ #libp2p
31
+
32
+ //map [id,addrs] of discovered peers (addrs is array of address)
33
+ #discoveredPeers
34
+
35
+ //array of all webpeers id has been found
36
+ #webPeersId
37
+
38
+ //map [id,addrs]
39
+ #webPeersAddrs
40
+
41
+ //database of best peers has been found
42
+ #dbstore
43
+ #dbstoreData
44
+
45
+ //map of [id,number_of_dialed] of good peers on #connectionTracker
46
+ #dialedGoodPeers
47
+
48
+ //boolean is dial websocket
49
+ #isDialWebsocket
50
+
51
+ //map [id,mddrs] of dialed bootstrap address
52
+ #dialedKnownBootstrap
53
+
54
+ //array of dialed discovered peers id
55
+ //#dialedDiscoveredPeers
56
+
57
+ //object from joinRoom()
58
+ #rooms
59
+
60
+ //map [id,addrs] of webpeers currently connected (addrs is array of address)
61
+ #connectedPeers
62
+
63
+ //array of we peers id proxy of #connectedPeers
64
+ #connectedPeersArr
65
+
66
+ //map [id,number_of_dialed] of #connectionTracker object store
67
+ #connectionTrackerStore
68
+
69
+ //map [id,addr] of all peers connections (addr is string of address)
70
+ #connections
71
+
72
+ //track disconnect event
73
+ #trackDisconnect
74
+
75
+ //list of dial multiaddress queue
76
+ #dialQueue
77
+
78
+ //is dial enabled
79
+ #isDialEnabled
80
+
81
+ id
82
+ status
83
+ IPFS
84
+ address
85
+ peers
86
+
87
+ constructor(libp2p,dbstore,onMetrics){
88
+
89
+ this.#libp2p = libp2p
90
+ this.#dbstore = dbstore
91
+ this.#dbstoreData = new Map()
92
+ this.#discoveredPeers = new Map()
93
+ this.#webPeersId = []
94
+ this.#webPeersAddrs = new Map()
95
+ this.#dialedGoodPeers = new Map()
96
+ this.#isDialWebsocket = false
97
+ this.#dialedKnownBootstrap = new Map()
98
+ //this.#dialedDiscoveredPeers = []
99
+ this.address = []
100
+ this.#rooms = {}
101
+ this.#connectedPeers = new Map()
102
+ this.#connectedPeersArr = []
103
+ this.#connectionTrackerStore = new Map()
104
+ this.#connections = new Map()
105
+ this.#trackDisconnect = new Map()
106
+ this.#dialQueue = []
107
+ this.#isDialEnabled = true
108
+
109
+ this.peers = (function(f) {
110
+ return f
111
+ })(this.#connectedPeersArr);
112
+
113
+ this.status = (function(libp2p) {
114
+ return libp2p.status
115
+ })(this.#libp2p);
116
+
117
+ this.IPFS = (function(libp2p,discoveredPeers) {
118
+ const obj = {libp2p,discoveredPeers}
119
+ return obj
120
+ })(this.#libp2p,this.#discoveredPeers);
121
+
122
+ this.id = this.#libp2p.peerId.toString()
123
+
124
+
125
+ //listen to peer connect event
126
+ this.#libp2p.addEventListener("peer:connect", (evt) => {
127
+
128
+ //console.log(`Connected to ${connection.toString()}`);
129
+
130
+ const connection = evt.detail;
131
+ const id = evt.detail.toString()
132
+
133
+ const connections = this.#libp2p.getConnections().map((con)=>{return {id:con.remotePeer.toString(),addr:con.remoteAddr.toString()}})
134
+ const connect = connections.find((con)=>con.id == id)
135
+ const addr = connect.addr
136
+ this.#connections.set(id,addr)
137
+
138
+ //required by joinRoom version 1 to announce via universal connectivity
139
+ if(connection.toString() === config.CONFIG_KNOWN_BOOTSTRAP_PEER_IDS[0]){
140
+ setTimeout(()=>{
141
+ this.#announce()
142
+ },1000)
143
+ }
144
+
145
+ });
146
+
147
+
148
+ //listen message from subscribed pupsub topic
149
+ this.#libp2p.services.pubsub.addEventListener('message', event => {
150
+
151
+ //console.log('on:'+event.detail.topic,event.detail.data)
152
+ //console.log('from '+event.detail.from.toString(),event)
153
+
154
+ if (event.detail.type !== 'signed') {
155
+ return
156
+ }
157
+ if(config.CONFIG_JOIN_ROOM_VERSION == 1){
158
+ const topic = event.detail.topic
159
+ const senderPeerId = event.detail.from.toString()
160
+ if(config.CONFIG_PUBSUB_PEER_DISCOVERY.includes(topic)){
161
+ try{
162
+
163
+ //if it is webpeer
164
+ if(this.#webPeersId.includes(senderPeerId)){
165
+
166
+ if(this.#connectedPeers.has(senderPeerId)){
167
+ //reset this last seen
168
+ const address = this.#connectedPeers.get(senderPeerId).addrs
169
+ const now = new Date().getTime()
170
+ const metadata = {addrs:address,last:now}
171
+ this.#connectedPeers.set(senderPeerId,metadata)
172
+ }
173
+ else{
174
+ //add to connected webpeers
175
+ this.#onConnectFn(senderPeerId)
176
+ const address = this.#webPeersAddrs.get(senderPeerId)
177
+ const now = new Date().getTime()
178
+ const metadata = {addrs:address,last:now}
179
+ this.#connectedPeers.set(senderPeerId,metadata)
180
+ }
181
+
182
+ //dial if not connected
183
+ if(!this.#isConnected(senderPeerId)){
184
+ if(this.#connections.has(senderPeerId)){
185
+ let mddrs = []
186
+ const addr = this.#connections.get(senderPeerId)
187
+ const mddr = multiaddr(addr)
188
+ mddrs.push(mddr)
189
+ this.#dialMultiaddress(mddrs)
190
+ }
191
+ else if(this.#discoveredPeers.has(senderPeerId)){
192
+ const addrs = this.#discoveredPeers.get(senderPeerId)
193
+ let mddrs = []
194
+ for(const addr of addrs){
195
+ const mddr = multiaddr(addr)
196
+ mddrs.push(mddr)
197
+ }
198
+ this.#dialMultiaddress(mddrs)
199
+ }
200
+ else{
201
+ const addrs = this.#connectedPeers.get(senderPeerId).addrs
202
+ let mddrs = []
203
+ for(const addr of addrs){
204
+ const mddr = multiaddr(addr)
205
+ mddrs.push(mddr)
206
+ }
207
+ this.#dialMultiaddress(mddrs)
208
+ }
209
+ }
210
+
211
+
212
+ }
213
+
214
+ //parse the message over pupsub peer discovery
215
+ const peer = PBPeer.decode(event.detail.data)
216
+ const msg = uint8ArrayToString(peer.addrs[0])
217
+ const json = JSON.parse(msg)
218
+ const prefix = json.prefix
219
+ const room = json.room
220
+ const message = json.message
221
+ const signal = json.signal
222
+ const id = json.id
223
+ //console.log(`from ${id}:${signal} = ${message}`)
224
+ if(id != senderPeerId)return
225
+ let address = json.address
226
+
227
+ //detect special webpeer identity
228
+ if(prefix === config.CONFIG_PREFIX){
229
+
230
+ //add to webpeers id
231
+ if(!this.#webPeersId.includes(id))this.#webPeersId.push(id)
232
+
233
+ //add to connected webpeers
234
+ if(!this.#connectedPeers.has(id)){
235
+ this.#onConnectFn(id)
236
+ address = []
237
+ const now = new Date().getTime()
238
+ const metadata = {addrs:address,last:now}
239
+ this.#connectedPeers.set(id,metadata)
240
+ this.#webPeersAddrs.set(id,address)
241
+ this.#connectedPeersArr.length = 0
242
+ for(const peer of this.#connectedPeers){
243
+ const item = {id:peer[0],address:peer[1].addrs}
244
+ this.#connectedPeersArr.push(item)
245
+ }
246
+ }
247
+
248
+
249
+ if(room){
250
+ if(this.#rooms[room]){
251
+
252
+ //inbound message
253
+ this.#rooms[room].onMessage(message,id)
254
+
255
+ //update room members
256
+ if(!this.#rooms[room].members.includes(id)){
257
+ this.#rooms[room].members.push(id)
258
+ this.#rooms[room].onMembers(this.#rooms[room].members)
259
+ }
260
+ }
261
+ }
262
+
263
+ if(signal){
264
+
265
+ //repply announce with ping
266
+ if(signal == 'announce'){
267
+ setTimeout(()=>{this.#ping()},1000)
268
+ }
269
+
270
+ if(signal == 'ping'){
271
+ //do nothing
272
+ }
273
+
274
+ //update connected webpeers
275
+ const now = new Date().getTime()
276
+ const metadata = {addrs:address,last:now}
277
+ this.#connectedPeers.set(id,metadata)
278
+ this.#webPeersAddrs.set(id,address)
279
+ this.#connectedPeersArr.length = 0
280
+ for(const peer of this.#connectedPeers){
281
+ const item = {id:peer[0],address:peer[1].addrs}
282
+ this.#connectedPeersArr.push(item)
283
+ }
284
+
285
+ }
286
+ }
287
+
288
+ }catch(err){
289
+ //console.log('from '+event.detail.from.toString())
290
+ console.debug(err)
291
+ }
292
+ }else{
293
+ const json = JSON.parse(topic)
294
+ const room = json.room
295
+ const message = new TextDecoder().decode(event.detail.data)
296
+ this.#rooms[room].onMessage(message)
297
+ }
298
+ }
299
+
300
+ })
301
+
302
+
303
+ //listen to peer discovery event
304
+ this.#libp2p.addEventListener('peer:discovery', (evt) => {
305
+
306
+ //console.log('Discovered:', evt.detail.id.toString())
307
+ //console.log('Discovered: '+evt.detail.id.toString(), evt.detail.multiaddrs.toString())
308
+
309
+ //save peer discover
310
+
311
+ const multiaddrs = evt.detail.multiaddrs
312
+ const id = evt.detail.id
313
+
314
+ if(multiaddrs.length != 0){
315
+ let addrs = []
316
+ for(const addr of multiaddrs){
317
+ let peeraddr
318
+ if(multiaddrs.toString().includes(evt.detail.id.toString())){
319
+ //console.log('Discovered:', evt.detail.multiaddrs.toString())
320
+ //peer from pupsub peer discovery already has included self id
321
+ peeraddr = addr.toString()
322
+ }
323
+ else{
324
+ //other need to add Id
325
+ peeraddr = addr.toString()+'/p2p/'+id
326
+ }
327
+ addrs.push(peeraddr)
328
+ }
329
+ //save the new format multiaddrs
330
+ this.#discoveredPeers.set(id.toString(), addrs)
331
+
332
+ //track if peer come from relay then dial it because there is a chance it is from other browser node
333
+ if(multiaddrs.toString().includes('certhash')&& multiaddrs.toString().includes('webtransport') && multiaddrs.toString().includes('p2p-circuit')){
334
+ //console.log(addrs)
335
+ if(!this.#connections.has(id)){
336
+ let mddrs = []
337
+ for(const addr of addrs){
338
+ const mddr = multiaddr(addr)
339
+ mddrs.push(mddr)
340
+ }
341
+ //this.#dialMultiaddress(mddrs)
342
+ }
343
+ }
344
+ }
345
+ else{
346
+ //peer with empty address (multiaddrs = [])
347
+ //this.#discoveredPeers.set(id.toString(), multiaddrs)
348
+ }
349
+
350
+ })
351
+
352
+
353
+ //listen to peer disconnect event
354
+ this.#libp2p.addEventListener("peer:disconnect",async (evt) => {
355
+
356
+ //const connection = evt.detail;
357
+ //console.log(`Disconnected from ${connection.toCID().toString()}`);
358
+ const id = evt.detail.string
359
+
360
+ //track disconnect event
361
+ if(this.#trackDisconnect.has(id)){
362
+ let count = this.#trackDisconnect.get(id)
363
+ count++
364
+ this.#trackDisconnect.set(id,count)
365
+ //console.log(this.#trackDisconnect)
366
+ if(count>5){
367
+ if(this.#dbstoreData.has(id)){
368
+ //await this.#dbstore.delete(new Key(id))
369
+ this.#dbstoreData.delete(id)
370
+ }
371
+
372
+ if(!this.#webPeersId.includes(id) && !this.#dialedKnownBootstrap.has(id)){
373
+ return
374
+ }
375
+ }
376
+ }
377
+ else{
378
+ this.#trackDisconnect.set(id,0)
379
+ }
380
+
381
+ //if this disconnected peer is web peer redial it
382
+ if(this.#webPeersId.includes(id)){
383
+ const addr = this.#connections.get(id)
384
+ let mddrs = []
385
+ const mddr = multiaddr(addr)
386
+ mddrs.push(mddr)
387
+ this.#dialMultiaddress(mddrs)
388
+ }
389
+
390
+ //if this disconnected peer is known bootstrap redial it
391
+ else if(this.#dialedKnownBootstrap.has(id)){
392
+ const addr = this.#connections.get(id)
393
+ let mddrs = []
394
+ const addrs = multiaddr(addr)
395
+ mddrs.push(addrs)
396
+ this.#dialMultiaddress(mddrs)
397
+ }
398
+
399
+ //redial if this disconnected peer is regular peer
400
+ else{
401
+ const addr = this.#connections.get(id)
402
+ let mddrs = []
403
+ const addrs = multiaddr(addr)
404
+ mddrs.push(addrs)
405
+ this.#dialMultiaddress(mddrs)
406
+ }
407
+ });
408
+
409
+
410
+ //listen to self peer update
411
+ this.#libp2p.addEventListener('self:peer:update', ({ detail: { peer } }) => {
412
+ //const multiaddrs = peer.addresses.map(({ multiaddr }) => multiaddr)
413
+ //console.log(`changed multiaddrs: peer ${peer.id.toString()} multiaddrs: ${multiaddrs}`)
414
+ const id = peer.id.toString()
415
+ const mddrs = []
416
+ peer.addresses.forEach((addr)=>{
417
+ const maddr = addr.multiaddr.toString()+'/p2p/'+id
418
+ if(maddr.includes('webtransport') && maddr.includes('certhash')){
419
+ mddrs.push(maddr)
420
+ }
421
+ })
422
+ //this.#ListenAddressChange(mddrs)
423
+ this.address = mddrs
424
+ this.#ping()
425
+ })
426
+
427
+ //dial known peers from configuration
428
+ this.#dialKnownPeers()
429
+
430
+ //watch connection every 30s if none dial known peers again from configuration
431
+ this.#watchConnection()
432
+
433
+ //if found good peers save to storage and reconnect if disconnect
434
+ this.#connectionTracker()
435
+
436
+ //periodically dial saved bootstrap address if disconnect
437
+ this.#dialRandomBootstrap()
438
+
439
+ //dial random discovered peers
440
+ //this.#dialdiscoveredpeers()
441
+
442
+ onMetrics((data)=>{
443
+ const signal = metrics(data)
444
+ this.#isDialEnabled = signal
445
+
446
+ })
447
+
448
+ setInterval(()=>{
449
+ this.#dialQueueList()
450
+ },5e3)
451
+
452
+ setInterval(()=>{
453
+ this.#trackLastSeen()
454
+ },5e3)
455
+
456
+ }
457
+
458
+
459
+
460
+
461
+ /*
462
+ PUBLIC FUNCTION
463
+ */
464
+
465
+ //Listen on new peer connection
466
+ #onConnectFn = () => {}
467
+ onJoin = f => (this.#onConnectFn = f)
468
+
469
+
470
+ //Listen on peer disconnect
471
+ #onDisconnectFn = () => {}
472
+ onLeave = f => (this.#onDisconnectFn = f)
473
+
474
+
475
+
476
+
477
+ /*
478
+ PRIVATE FUNCTION
479
+ */
480
+
481
+
482
+ //check the last seen in web peer
483
+ #trackLastSeen(){
484
+ const timeout = 25*1000
485
+ const now = new Date().getTime()
486
+
487
+ //if webpeer last seen grather then timeout send onDisconnect
488
+ for(const peer of this.#connectedPeers){
489
+ const id = peer[0]
490
+ const last = peer[1].last
491
+ const time = now-last
492
+ if(time>timeout){
493
+ this.#connectedPeers.delete(id)
494
+ this.#connectedPeersArr.length = 0
495
+ for(const peer of this.#connectedPeers){
496
+ const item = {id:peer[0],address:peer[1].addrs}
497
+ this.#connectedPeersArr.push(item)
498
+ }
499
+ this.#onDisconnectFn(id)
500
+
501
+ //remove id from room member
502
+ const rooms = Object.keys(this.#rooms)
503
+ for(const room of rooms){
504
+ if(this.#rooms[room].members.includes(id)){
505
+ const index = this.#rooms[room].members.indexOf(id)
506
+ this.#rooms[room].members.splice(index,1)
507
+ this.#rooms[room].onMembers(this.#rooms[room].members)
508
+ }
509
+ }
510
+ }
511
+ }
512
+ }
513
+
514
+ //check if this id is connected
515
+ #isConnected(id){
516
+ let peers = []
517
+ for(const peer of this.#libp2p.getPeers()){
518
+ peers.push(peer.toString())
519
+ }
520
+ if(peers.includes(id)){
521
+ return true
522
+ }
523
+ else{
524
+ return false
525
+ }
526
+ }
527
+
528
+
529
+ //add multiaddr address to queue list
530
+ #dialMultiaddress(mddrs){
531
+ if(mddrs.length>0){
532
+
533
+ const id = mddrs[0].toString().split('/').pop()
534
+
535
+ const ids = this.#dialQueue.map((arr)=> arr[0].toString().split('/').pop())
536
+
537
+ //if peer id is already in the queque cancel queque
538
+ if(ids.includes(id)){
539
+ return
540
+ }
541
+
542
+ if(this.#webPeersId.includes(id) || id == config.CONFIG_KNOWN_BOOTSTRAP_PEER_IDS[0] ){
543
+ this.#dialQueue.unshift(mddrs)
544
+ }
545
+ else{
546
+ this.#dialQueue.push(mddrs)
547
+ }
548
+
549
+ }
550
+ }
551
+
552
+ //dial multiaddr address in queue list
553
+ #dialQueueList(){
554
+
555
+ if(!this.#isDialEnabled)return
556
+
557
+ const mddrsToDial = 3
558
+
559
+ let queue = []
560
+ for(const item of this.#libp2p.getDialQueue()){
561
+ const id = item.peerId.string
562
+ queue.push(id)
563
+ }
564
+
565
+ if (queue.length > mddrsToDial)return
566
+
567
+ for(let i = 0; i < mddrsToDial; i++){
568
+ const mddrs = this.#dialQueue.shift()
569
+ if(mddrs != undefined && mddrs.length>0){
570
+
571
+ const id = mddrs[0].toString().split('/').pop()
572
+
573
+ if(this.#isConnected(id))continue
574
+ if(queue.includes(id)){continue;}
575
+
576
+ //dial with webtransport
577
+ this.#dialWebtransport(mddrs)
578
+
579
+ //fallback dial with websocket if enabled
580
+ if(this.#isDialWebsocket){
581
+ this.#dialWebsocket(mddrs)
582
+ }
583
+
584
+ }
585
+ else{
586
+ break
587
+ }
588
+ }
589
+
590
+ }
591
+
592
+
593
+ //announce and ping via pupsub peer discovery
594
+ async #announce(){
595
+ const topics = config.CONFIG_PUBSUB_PEER_DISCOVERY
596
+ const data = JSON.stringify({prefix:config.CONFIG_PREFIX,signal:'announce',id:this.#libp2p.peerId.toString(),address:this.address})
597
+ const peer = {
598
+ publicKey: this.#libp2p.peerId.publicKey,
599
+ addrs: [uint8ArrayFromString(data)],
600
+ }
601
+ const encodedPeer = PBPeer.encode(peer)
602
+ for(const topic of topics){
603
+ await this.#libp2p.services.pubsub.publish(topic, encodedPeer)
604
+ }
605
+ }
606
+ async #ping(){
607
+ const topics = config.CONFIG_PUBSUB_PEER_DISCOVERY
608
+ const data = JSON.stringify({prefix:config.CONFIG_PREFIX,signal:'ping',id:this.#libp2p.peerId.toString(),address:this.address})
609
+ const peer = {
610
+ publicKey: this.#libp2p.peerId.publicKey,
611
+ addrs: [uint8ArrayFromString(data)],
612
+ }
613
+ const encodedPeer = PBPeer.encode(peer)
614
+ for(const topic of topics){
615
+ await this.#libp2p.services.pubsub.publish(topic, encodedPeer)
616
+ }
617
+ }
618
+
619
+
620
+ joinRoom = room => {
621
+ if (this.#rooms[room]) {
622
+ return [
623
+ this.#rooms[room].sendMessage,
624
+ this.#rooms[room].listenMessage,
625
+ this.#rooms[room].onMembersChange
626
+ ]
627
+
628
+
629
+ }
630
+
631
+ if (!room) {
632
+ throw mkErr('room is required')
633
+ }
634
+
635
+ //join room version 1 user pupsub via pupsub peer discovery
636
+ if(config.CONFIG_JOIN_ROOM_VERSION == 1){
637
+
638
+ const topics = config.CONFIG_PUBSUB_PEER_DISCOVERY
639
+
640
+ this.#rooms[room] = {
641
+ onMessage : () => {},
642
+ listenMessage : f => (this.#rooms[room] = {...this.#rooms[room], onMessage: f}),
643
+ sendMessage : async (message) => {
644
+ const data = JSON.stringify({prefix:config.CONFIG_PREFIX,room,message,id:this.#libp2p.peerId.toString()})
645
+ const peer = {
646
+ publicKey: this.#libp2p.peerId.publicKey,
647
+ addrs: [uint8ArrayFromString(data)],
648
+ }
649
+ const encodedPeer = PBPeer.encode(peer)
650
+ for(const topic of topics){
651
+ await this.#libp2p.services.pubsub.publish(topic, encodedPeer)
652
+ }
653
+ },
654
+ members : [this.id],
655
+ onMembers : () => {},
656
+ onMembersChange : f => {this.#rooms[room] = {...this.#rooms[room], onMembers: f};this.#rooms[room].onMembers(this.#rooms[room].members)},
657
+ }
658
+ }
659
+
660
+ return [
661
+ this.#rooms[room].sendMessage,
662
+ this.#rooms[room].listenMessage,
663
+ this.#rooms[room].onMembersChange
664
+ ]
665
+ }
666
+
667
+
668
+ //dial discovered peers
669
+ /*#dialdiscoveredpeers(){
670
+ setInterval(()=>{
671
+ const keys = Array.from(this.#discoveredPeers.keys())
672
+ for(const key of keys){
673
+ if(!this.#dialedDiscoveredPeers.includes(key)){
674
+ this.#dialedDiscoveredPeers.push(key)
675
+ const addrs = this.#discoveredPeers.get(key)
676
+ let mddrs = []
677
+ for(const addr of addrs){
678
+ const mddr = multiaddr(addr)
679
+ mddrs.push(mddr)
680
+ }
681
+ this.#dialMultiaddress(mddrs)
682
+ break
683
+ }
684
+ }
685
+ },30*1000)
686
+ }*/
687
+
688
+
689
+ //dial random known bootstrap periodically
690
+ #dialRandomBootstrap(){
691
+ setInterval(()=>{
692
+ //const keys = Array.from(this.#dialedKnownBootstrap.keys())
693
+ const keys = config.CONFIG_KNOWN_BOOTSTRAP_PEER_IDS
694
+ const randomKey = Math.floor(Math.random() * keys.length)
695
+ let ids = []
696
+ ids.push(keys[randomKey])
697
+
698
+ //universal connectivity id for webpeer discovery and joinRoom version 1 to work
699
+ ids.push(config.CONFIG_KNOWN_BOOTSTRAP_PEER_IDS[0])
700
+
701
+ for(const id of ids){
702
+ if(id == undefined)continue
703
+ //const addrs = this.#dialedKnownBootstrap.get(id)
704
+
705
+ if(!this.#isConnected(id)){
706
+ if(this.#connections.has(id))
707
+ {
708
+ let mddrs = []
709
+ const addr = this.#connections.get(id)
710
+ const mddr = multiaddr(addr)
711
+ mddrs.push(mddr)
712
+ this.#dialMultiaddress(mddrs)
713
+ }
714
+ else if (this.#dialedKnownBootstrap.has(id)){
715
+ let mddrs = []
716
+ const addrs = this.#dialedKnownBootstrap.get(id)
717
+ for(const addr of addrs){
718
+ const mddr = multiaddr(addr)
719
+ mddrs.push(mddr)
720
+ }
721
+ this.#dialMultiaddress(mddrs)
722
+ }
723
+ else{
724
+ const bootstrap = config.CONFIG_KNOWN_BOOTSTRAP_PEERS_ADDRS
725
+ const index = bootstrap.findIndex((peer)=>peer.Peers[0].ID == id)
726
+ const addrs = bootstrap[index].Peers[0].Addrs
727
+ let mddrs = []
728
+ for(const addr of addrs){
729
+ const peeraddr = addr+'/p2p/'+id
730
+ const mddr = multiaddr(peeraddr)
731
+ mddrs.push(mddr)
732
+ }
733
+ this.#dialMultiaddress(mddrs)
734
+ }
735
+ }
736
+ }
737
+ },45*1000)
738
+ }
739
+
740
+
741
+ //track for good connection
742
+ async #connectionTracker(){
743
+
744
+ for await (const { key, value } of this.#dbstore.query({})) {
745
+ const id = key.toString().split('/')[1]
746
+ const addr = new TextDecoder().decode(value)
747
+ this.#dbstoreData.set(id,addr)
748
+ }
749
+
750
+ setInterval(async ()=>{
751
+
752
+ //save peer address if connection is good
753
+ const connections = this.#libp2p.getConnections()
754
+ for(const connect of connections){
755
+ const peer = connect.remotePeer
756
+ const remote = connect.remoteAddr
757
+ const upgraded = connect.timeline.upgraded
758
+ const bestlimit = 5*60*1000
759
+ const now = new Date().getTime()
760
+ const besttime = now-upgraded
761
+ if(besttime>bestlimit){
762
+ const addr = remote.toString()
763
+ const id = peer.toString()
764
+ if(!this.#webPeersId.includes(id) && !config.CONFIG_KNOWN_BOOTSTRAP_PEER_IDS.includes(id) && !this.#dbstoreData.get(id) && !addr.includes('p2p-circuit')){
765
+ //await this.#dbstore.delete(new Key(id))
766
+ await this.#dbstore.put(new Key(id), new TextEncoder().encode(addr))
767
+ this.#dbstoreData.set(id,addr)
768
+ }
769
+ }
770
+ const goodlimit = 60*1000
771
+ const goodtime = now-upgraded
772
+ if(goodtime>goodlimit){
773
+ const id = peer.toString()
774
+ if(!this.#dialedGoodPeers.has(id))this.#dialedGoodPeers.set(id,0)
775
+ }
776
+
777
+ }
778
+
779
+
780
+ let peers = []
781
+ for(const peer of this.#libp2p.getPeers()){
782
+ peers.push(peer.toString())
783
+ }
784
+
785
+
786
+ //connect to saved best peer address
787
+ //working great
788
+ for(const peer of this.#dbstoreData){
789
+ const id = peer[0]
790
+ const addr = peer[1]
791
+ if(peers.includes(id)){
792
+ this.#connectionTrackerStore.set(id,0)
793
+ continue
794
+ }else{
795
+ if(this.#connectionTrackerStore.has(id)){
796
+ let current = this.#connectionTrackerStore.get(id)
797
+ if(current>10)continue
798
+ current++
799
+ this.#connectionTrackerStore.set(id,current)
800
+ }
801
+ else{
802
+ this.#connectionTrackerStore.set(id,0)
803
+ }
804
+ let mddrs = []
805
+ const mddr = multiaddr(addr)
806
+ mddrs.push(mddr)
807
+ this.#dialMultiaddress(mddrs)
808
+ }
809
+ }
810
+
811
+ //connect to good peer address if it is disconnected
812
+ const goods = Array.from(this.#dialedGoodPeers.keys())
813
+ for(const id of goods){
814
+ if(peers.includes(id)){
815
+ this.#dialedGoodPeers.set(id,0)
816
+ continue
817
+ }
818
+ else{
819
+
820
+ let count = this.#dialedGoodPeers.get(id)
821
+ if (count < 15 || (count < 25 && this.#dialedKnownBootstrap.has(id))){
822
+ const addr = this.#connections.get(id)
823
+ let mddrs = []
824
+ const mddr = multiaddr(addr)
825
+ mddrs.push(mddr)
826
+ this.#dialMultiaddress(mddrs)
827
+ count++
828
+ this.#dialedGoodPeers.set(id,count)
829
+ }
830
+ }
831
+ }
832
+
833
+ },30*1000)
834
+ }
835
+
836
+
837
+ //update listen address on change
838
+ //#ListenAddressChange = () => {}
839
+ //#onSelfAddress = f => (this.#ListenAddressChange = f)
840
+
841
+
842
+ //Periodically watch for connection
843
+ #watchConnection(){
844
+ setInterval(()=>{
845
+ const peers = this.#libp2p.getPeers().length
846
+ if(peers == 0){
847
+ this.#dialKnownPeers()
848
+ }
849
+ },60*1000)
850
+ }
851
+
852
+
853
+ //dial to all known bootstrap peers and DNS
854
+ #dialKnownPeers(){
855
+ this.#dialKnownBootstrap()
856
+ setTimeout(()=>{
857
+ const peers = this.#libp2p.getPeers().length
858
+ if(peers == 0){
859
+ //currently not needed
860
+ //this.#dialKnownID()
861
+ setTimeout(()=>{
862
+ const peers = this.#libp2p.getPeers().length
863
+ if(peers == 0){
864
+ //currently not needed
865
+ //this.#dialKnownDNS()
866
+ setTimeout(()=>{
867
+ const peers = this.#libp2p.getPeers().length
868
+ if(peers == 0){
869
+ //currently not needed
870
+ //this.#dialKnownDNSonly()
871
+ }
872
+ },15000)
873
+ }
874
+ },15000)
875
+ }
876
+ },15000)
877
+ }
878
+
879
+
880
+ //dial based on known bootsrap peers address
881
+ #dialKnownBootstrap(){
882
+ const bootstrap = config.CONFIG_KNOWN_BOOTSTRAP_PEERS_ADDRS
883
+ for(const peer of bootstrap){
884
+ const address = peer.Peers[0].Addrs
885
+ const id = peer.Peers[0].ID
886
+ let mddrs = []
887
+ let addrs = []
888
+ for(const addr of address){
889
+ const peeraddr = addr+'/p2p/'+id
890
+ const peermddr = multiaddr(peeraddr)
891
+ addrs.push(peeraddr)
892
+ mddrs.push(peermddr)
893
+ }
894
+
895
+ this.#dialedKnownBootstrap.set(id,addrs)
896
+ if(!this.#isConnected(id)){
897
+ this.#dialMultiaddress(mddrs)
898
+ }
899
+
900
+ }
901
+ }
902
+
903
+
904
+ //dial based on known peers ID
905
+ /*async #dialKnownID(){
906
+ const api = config.CONFIG_DELEGATED_API
907
+ const delegatedClient = createDelegatedRoutingV1HttpApiClient(api)
908
+ const BOOTSTRAP_PEER_IDS = config.CONFIG_KNOWN_BOOTSTRAP_PEER_IDS
909
+ const peers = await Promise.all(
910
+ BOOTSTRAP_PEER_IDS.map((peerId) => first(delegatedClient.getPeers(peerIdFromString(peerId)))),
911
+ )
912
+ for(const peer of peers){
913
+ if(!peer)return
914
+ const address = peer.Addrs
915
+ const id = peer.ID
916
+ let mddrs = []
917
+ let addrs = []
918
+ for(const addr of address){
919
+ const peeraddr = addr.toString()+'/p2p/'+id.toString()
920
+ const peermddr = multiaddr(peeraddr)
921
+ addrs.push(peeraddr)
922
+ mddrs.push(peermddr)
923
+ }
924
+
925
+ this.#dialedKnownBootstrap.set(id,addrs)
926
+ if(!this.#isConnected(id)){
927
+ this.#dialMultiaddress(mddrs)
928
+ }
929
+ }
930
+ }*/
931
+
932
+
933
+ //dial based on known bootstrap DNS
934
+ /*async #dialKnownDNS(){
935
+ const dnsresolver = config.CONFIG_DNS_RESOLVER
936
+ const bootstrapdns = config.CONFIG_KNOWN_BOOTSTRAP_DNS
937
+ const response = await fetch(dnsresolver+'?name='+bootstrapdns+'&type=txt')
938
+ const json = await response.json()
939
+ const dns = json.Answer
940
+ const BOOTSTRAP_PEER_IDS = []
941
+ for(const dnsaddr of dns){
942
+ const id = dnsaddr.data.split('/').pop()
943
+ BOOTSTRAP_PEER_IDS.push(id)
944
+ }
945
+ const api = config.CONFIG_DELEGATED_API
946
+ const delegatedClient = createDelegatedRoutingV1HttpApiClient(api)
947
+ const peers = await Promise.all(
948
+ BOOTSTRAP_PEER_IDS.map((peerId) => first(delegatedClient.getPeers(peerIdFromString(peerId)))),
949
+ )
950
+ for(const peer of peers){
951
+ const address = peer.Addrs
952
+ const id = peer.ID
953
+ let mddrs = []
954
+ let addrs = []
955
+ for(const addr of address){
956
+ const peeraddr = addr.toString()+'/p2p/'+id.toString()
957
+ const peermddr = multiaddr(peeraddr)
958
+ addrs.push(peeraddr)
959
+ mddrs.push(peermddr)
960
+ }
961
+
962
+ this.#dialedKnownBootstrap.set(id,addrs)
963
+ if(!this.#isConnected(id)){
964
+ this.#dialMultiaddress(mddrs)
965
+ }
966
+ }
967
+
968
+ }*/
969
+
970
+
971
+ //dial based on known bootstrap DNS using DNS resolver only
972
+ /*async #dialKnownDNSonly(){
973
+ const dnsresolver = config.CONFIG_DNS_RESOLVER
974
+ const bootstrapdns = config.CONFIG_KNOWN_BOOTSTRAP_DNS
975
+ const response = await fetch(dnsresolver+'?name='+bootstrapdns+'&type=txt')
976
+ const json = await response.json()
977
+ const dns = json.Answer
978
+
979
+ for(const dnsitem of dns){
980
+ const arr = dnsitem.data.split('/')
981
+ const id = arr.pop()
982
+ const dnsaddr = '_dnsaddr.'+arr[2]
983
+ this.#dialDNSWebsocketWebtransport(id,dnsaddr)
984
+ }
985
+ }*/
986
+
987
+
988
+ //dial DNS with webtransport and websocket
989
+ /*async #dialDNSWebsocketWebtransport(id,dnsaddr){
990
+ const dnsresolver = config.CONFIG_DNS_RESOLVER
991
+ const response = await fetch(dnsresolver+'?name='+dnsaddr+'&type=txt')
992
+ const json = await response.json()
993
+ const dns = json.Answer
994
+ let mddrs = []
995
+ let addrs = []
996
+ for(const dnsitem of dns){
997
+ const arr = dnsitem.data.split('=')
998
+ const dnsaddr = arr[1]
999
+ const maddr = multiaddr(dnsaddr)
1000
+ mddrs.push(maddr)
1001
+ addrs.push(dnsaddr)
1002
+ }
1003
+
1004
+
1005
+ this.#isDialWebsocket = true
1006
+ this.#dialedKnownBootstrap.set(id,addrs)
1007
+
1008
+ this.#dialedKnownBootstrap.set(id,addrs)
1009
+ if(!this.#isConnected(id)){
1010
+ this.#dialMultiaddress(mddrs)
1011
+ this.#dialWebsocket(mddrs)
1012
+ }
1013
+ }*/
1014
+
1015
+
1016
+ //dial only webtransport multiaddrs
1017
+ async #dialWebtransport(multiaddrs){
1018
+ const webTransportMadrs = multiaddrs.filter((maddr) => maddr.protoNames().includes('webtransport')&&maddr.protoNames().includes('certhash'))
1019
+ for (const addr of webTransportMadrs) {
1020
+ try {
1021
+ //console.log(`attempting to dial webtransport multiaddr: %o`, addr.toString())
1022
+ await this.#libp2p.dial(addr)
1023
+ return // if we succeed dialing the peer, no need to try another address
1024
+ } catch (error) {
1025
+ //console.log(`failed to dial webtransport multiaddr: %o`, addr.toString())
1026
+ console.debug(error)
1027
+ }
1028
+ }
1029
+ }
1030
+
1031
+ //dial only webtransport multiaddrs
1032
+ /*#dialWebtransport1(multiaddrs){
1033
+ const webTransportMadrs = multiaddrs.filter((maddr) => maddr.protoNames().includes('webtransport')&&maddr.protoNames().includes('certhash'))
1034
+ if(webTransportMadrs.length == 0)return
1035
+ this.#libp2p.dial(webTransportMadrs).then((data)=>{console.warn(data)},(data)=>{console.warn(data)})
1036
+ }*/
1037
+
1038
+ //dial only websocket multiaddrs
1039
+ async #dialWebsocket(multiaddrs){
1040
+ const webSocketMadrs = multiaddrs.filter((maddr) => maddr.protoNames().includes('wss'))
1041
+ for (const addr of webSocketMadrs) {
1042
+ try {
1043
+ //console.log(`attempting to dial websocket multiaddr: %o`, addr)
1044
+ await this.#libp2p.dial(addr)
1045
+ return // if we succeed dialing the peer, no need to try another address
1046
+ } catch (error) {
1047
+ //console.log(`failed to dial websocket multiaddr: %o`, addr)
1048
+ console.debug(error)
1049
+ }
1050
+ }
1051
+ }
1052
+
1053
+
1054
+ //entry point to webpeerjs
1055
+ static async createWebpeer(){
1056
+
1057
+ // all libp2p debug logs
1058
+ localStorage.setItem('debug', 'libp2p:*')
1059
+
1060
+ const dbstore = new IDBDatastore(config.CONFIG_DBSTORE_PATH)
1061
+ await dbstore.open()
1062
+
1063
+ const bootstrapAddrs = []
1064
+
1065
+ //let addrs = []
1066
+ const getbootstrap = config.CONFIG_KNOWN_BOOTSTRAP_PEERS_ADDRS
1067
+ for(const peer of getbootstrap){
1068
+ const addrs = peer.Peers[0].Addrs
1069
+ const id = peer.Peers[0].ID
1070
+ //let mddrs = []
1071
+ for(const addr of addrs){
1072
+ if(addr.includes('webtransport')&&addr.includes('certhash')){
1073
+ bootstrapAddrs.push(addr+'/p2p/'+id)
1074
+ }
1075
+ }
1076
+ }
1077
+
1078
+ let onMetricsFn = () => {}
1079
+ const onMetrics = f => (onMetricsFn = f)
1080
+
1081
+ //create libp2p instance
1082
+ const libp2p = await createLibp2p({
1083
+ addresses: {
1084
+ listen: [
1085
+ ],
1086
+ },
1087
+ transports:[
1088
+ webTransport(),
1089
+ circuitRelayTransport({
1090
+ discoverRelays: config.CONFIG_DISCOVER_RELAYS,
1091
+ reservationConcurrency: 1,
1092
+ maxReservationQueueLength: 3
1093
+ }),
1094
+ ],
1095
+ connectionManager: {
1096
+ maxConnections: config.CONFIG_MAX_CONNECTIONS,
1097
+ minConnections: config.CONFIG_MIN_CONNECTIONS,
1098
+ autoDialInterval:60e3,
1099
+ autoDialConcurrency:0,
1100
+ autoDialMaxQueueLength:0,
1101
+ autoDialPriority:1000,
1102
+ autoDialDiscoveredPeersDebounce:30e3,
1103
+ maxParallelDials: 3,
1104
+ dialTimeout: 5e3,
1105
+ maxIncomingPendingConnections: 5,
1106
+ maxDialQueueLength:10,
1107
+ inboundConnectionThreshold:3,
1108
+ maxPeerAddrsToDial:2,
1109
+ inboundUpgradeTimeout:5e3
1110
+ },
1111
+ connectionEncryption: [noise()],
1112
+ streamMuxers: [
1113
+ yamux({
1114
+ maxInboundStreams: 50,
1115
+ maxOutboundStreams: 50,
1116
+ })
1117
+ ],
1118
+ connectionGater: {
1119
+ filterMultiaddrForPeer: async (peer, multiaddrTest) => {
1120
+ const multiaddrString = multiaddrTest.toString();
1121
+ if (
1122
+ multiaddrString.includes("/ip4/127.0.0.1") ||
1123
+ multiaddrString.includes("/ip6/")
1124
+ ) {
1125
+ return false;
1126
+ }
1127
+ return true;
1128
+ },
1129
+ denyDialMultiaddr: async (multiaddrTest) => {
1130
+ const multiaddrString = multiaddrTest.toString();
1131
+ if (
1132
+ multiaddrString.includes("/ip4/127.0.0.1") ||
1133
+ multiaddrString.includes("/ip6/")
1134
+ ) {
1135
+ return true;
1136
+ }
1137
+ return false;
1138
+ },
1139
+ },
1140
+ peerDiscovery: [
1141
+ pubsubPeerDiscovery({
1142
+ interval: 10_000,
1143
+ topics: config.CONFIG_PUBSUB_PEER_DISCOVERY,
1144
+ listenOnly: false,
1145
+ }),
1146
+
1147
+ ],
1148
+ services: {
1149
+ pubsub: gossipsub({
1150
+ allowPublishToZeroTopicPeers: true,
1151
+ msgIdFn: msgIdFnStrictNoSign,
1152
+ ignoreDuplicatePublishError: true,
1153
+ }),
1154
+ identify: identify(),
1155
+ identifyPush: identifyPush(),
1156
+ aminoDHT: kadDHT({
1157
+ protocol: '/ipfs/kad/1.0.0',
1158
+ peerInfoMapper: removePrivateAddressesMapper,
1159
+ clientMode: false
1160
+ }),
1161
+
1162
+ },
1163
+ peerStore: {
1164
+ persistence: true,
1165
+ threshold: 1
1166
+ },
1167
+ metrics: simpleMetrics({
1168
+ onMetrics: (metrics) => {onMetricsFn(metrics)},
1169
+ intervalMs: 1000
1170
+ })
1171
+ })
1172
+
1173
+
1174
+
1175
+ //console.log(`Node started with id ${libp2p.peerId.toString()}`)
1176
+
1177
+ //DHT server mode act as bootstrap peer in IPFS network
1178
+ await libp2p.services.aminoDHT.setMode("server")
1179
+
1180
+
1181
+ //return webpeerjs class
1182
+ return new webpeerjs(libp2p,dbstore,onMetrics)
1183
+ }
1184
+ }
1185
+
1186
+
1187
+ //export module
1188
+ export {webpeerjs}