1
+ import { createLibp2p } from "libp2p" ;
2
+ import { webRTC } from "@libp2p/webrtc" ;
3
+ import { webSockets } from "@libp2p/websockets" ;
4
+ import { tcp } from "@libp2p/tcp" ;
5
+ import { noise } from "@chainsafe/libp2p-noise" ;
6
+ import { circuitRelayTransport } from "@libp2p/circuit-relay-v2" ;
7
+ import { identify , identifyPush } from "@libp2p/identify" ;
8
+ import { dcutr } from "@libp2p/dcutr" ;
9
+ import { autoNAT } from "@libp2p/autonat" ;
10
+ import { yamux } from "@chainsafe/libp2p-yamux" ;
11
+ import { pubsubPeerDiscovery } from "@libp2p/pubsub-peer-discovery" ;
12
+ import { gossipsub } from "@chainsafe/libp2p-gossipsub" ;
13
+ import { CHAT_FILE_TOPIC , CHAT_TOPIC , PUBSUB_PEER_DISCOVERY } from "./constants.js" ;
14
+ import { kadDHT } from "@libp2p/kad-dht" ;
15
+ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' ;
16
+ import { toString as uint8ArrayToString } from 'uint8arrays/to-string' ;
17
+ import { sha256 } from 'multiformats/hashes/sha2' ;
18
+ import { stdinToStream , streamToConsole } from './stream.js' ;
19
+
20
+ // Define the universal connectivity protocol constant
21
+ const UNIVERSAL_PROTOCOL = '/universal/1.0.0' ;
22
+
23
+ export async function createNode ( ) {
24
+ const node = await createLibp2p ( {
25
+ addresses : {
26
+ listen : [
27
+ "/ip4/0.0.0.0/tcp/0/ws" ,
28
+ "/ip4/0.0.0.0/tcp/0" ,
29
+ '/webrtc' ,
30
+ '/webrtc-direct' ,
31
+ ] ,
32
+ } ,
33
+ transports : [
34
+ webSockets ( ) ,
35
+ tcp ( ) ,
36
+ webRTC ( ) ,
37
+ circuitRelayTransport ( { discoverRelays : 1 } )
38
+ ] ,
39
+ connectionEncrypters : [ noise ( ) ] ,
40
+ connectionManager : {
41
+ maxConnections : 100 ,
42
+ minConnections : 5 ,
43
+ autoDialInterval : 30000 ,
44
+ dialTimeout : 30000 ,
45
+ } ,
46
+ connectionGater : {
47
+ denyDialMultiaddr : async ( { multiaddr } ) => false ,
48
+ } ,
49
+ streamMuxers : [ yamux ( ) ] ,
50
+ services : {
51
+ pubsub : gossipsub ( {
52
+ allowPublishToZeroTopicPeers : true ,
53
+ msgIdFn : msgIdFnStrictNoSign ,
54
+ ignoreDuplicatePublishError : true ,
55
+ } ) ,
56
+ identify : identify ( ) ,
57
+ identifyPush : identifyPush ( ) ,
58
+ dcutr : dcutr ( ) ,
59
+ kadDHT : kadDHT ( ) ,
60
+ autoNAT : autoNAT ( {
61
+ protocolPrefix : "libp2p" ,
62
+ startupDelay : 5000 ,
63
+ refreshInterval : 60000 ,
64
+ } ) ,
65
+ } ,
66
+ peerDiscovery : [
67
+ pubsubPeerDiscovery ( {
68
+ interval : 30000 ,
69
+ topics : [ PUBSUB_PEER_DISCOVERY ] ,
70
+ listenOnly : false ,
71
+ } ) ,
72
+ ] ,
73
+ } ) ;
74
+ return node ;
75
+ }
76
+
77
+ export async function msgIdFnStrictNoSign ( msg ) {
78
+ const enc = new TextEncoder ( ) ;
79
+ const encodedSeqNum = enc . encode ( msg . sequenceNumber . toString ( ) ) ;
80
+ return await sha256 . encode ( encodedSeqNum ) ;
81
+ }
82
+
83
+ /**
84
+ * Helper to dial a target multiaddr using the specified protocol.
85
+ * Sets up interactive pipes for stdin and stdout.
86
+ */
87
+ async function robustDial ( sourceNode , targetMultiaddr , protocol = UNIVERSAL_PROTOCOL ) {
88
+ try {
89
+ console . log ( `Attempting to dial ${ targetMultiaddr } using protocol ${ protocol } ` ) ;
90
+ const stream = await sourceNode . dialProtocol ( targetMultiaddr , protocol ) ;
91
+ console . log ( `Successfully dialed ${ targetMultiaddr } with protocol ${ protocol } ` ) ;
92
+ // Set up interactive communication
93
+ stdinToStream ( stream ) ;
94
+ streamToConsole ( stream ) ;
95
+ return stream ;
96
+ } catch ( error ) {
97
+ console . error ( `Failed to dial ${ targetMultiaddr } using protocol ${ protocol } : ${ error . message } ` ) ;
98
+ throw error ;
99
+ }
100
+ }
101
+
102
+ async function main ( ) {
103
+ // Create two nodes concurrently for testing purposes.
104
+ const [ node1 , node2 ] = await Promise . all ( [ createNode ( ) , createNode ( ) ] ) ;
105
+
106
+ console . log ( `Node1 ID: ${ node1 . peerId . toString ( ) } ` ) ;
107
+ node1 . getMultiaddrs ( ) . forEach ( addr => console . log ( `Node1 listening on: ${ addr . toString ( ) } ` ) ) ;
108
+
109
+ console . log ( `Node2 ID: ${ node2 . peerId . toString ( ) } ` ) ;
110
+ node2 . getMultiaddrs ( ) . forEach ( addr => console . log ( `Node2 listening on: ${ addr . toString ( ) } ` ) ) ;
111
+
112
+ // // Setup universal protocol handler on node2.
113
+ node2 . handle ( UNIVERSAL_PROTOCOL , async ( { stream, connection } ) => {
114
+ console . log ( `Node2 received connection on ${ UNIVERSAL_PROTOCOL } from ${ connection . remotePeer . toString ( ) } ` ) ;
115
+ try {
116
+ // Establish interactive communication.
117
+ stdinToStream ( stream ) ;
118
+ streamToConsole ( stream ) ;
119
+ } catch ( err ) {
120
+ console . log ( 'Error in universal protocol handler on node2:' , err . message ) ;
121
+ }
122
+ } ) ;
123
+
124
+ // Directly dial node2 from node1 using one of node2's multiaddrs.
125
+ const targetAddr = node2 . getMultiaddrs ( ) [ 0 ] ;
126
+ if ( targetAddr ) {
127
+ await robustDial ( node1 , targetAddr , UNIVERSAL_PROTOCOL ) ;
128
+ } else {
129
+ console . warn ( 'No multiaddr found for node2' ) ;
130
+ }
131
+
132
+ // Log new connections on node1.
133
+ node1 . addEventListener ( 'connection:open' , ( evt ) => {
134
+ try {
135
+ const conn = evt . detail ;
136
+ console . log ( `Node1: New connection opened from peer ${ conn . remotePeer } ` ) ;
137
+ console . log ( 'Connection details:' , conn ) ;
138
+ } catch ( err ) {
139
+ console . log ( 'Error in connection:open listener on node1:' , err . message ) ;
140
+ }
141
+ } ) ;
142
+
143
+ // When a peer is discovered, attempt to dial using the universal protocol.
144
+ node2 . addEventListener ( 'peer:discovery' , async ( evt ) => {
145
+ console . info ( 'Node1 discovered peer:' , evt . detail ) ;
146
+ const discoveredMultiaddrs = evt . detail . id ;
147
+ if ( discoveredMultiaddrs && discoveredMultiaddrs . length > 0 ) {
148
+ try {
149
+ await robustDial ( node1 , discoveredMultiaddrs , UNIVERSAL_PROTOCOL ) ;
150
+ } catch ( error ) {
151
+ console . log ( 'Error dialing discovered peer:' , error . message ) ;
152
+ }
153
+ }
154
+ } ) ;
155
+
156
+ // Setup pubsub subscriptions and logging.
157
+ node1 . services . pubsub . subscribe ( CHAT_TOPIC ) ;
158
+ node1 . services . pubsub . addEventListener ( 'message' , ( evt ) => {
159
+ console . log ( `Node1 received on topic ${ evt . detail . topic } : ${ uint8ArrayToString ( evt . detail . data ) } ` ) ;
160
+ } ) ;
161
+
162
+ node2 . services . pubsub . subscribe ( CHAT_TOPIC ) ;
163
+ node2 . services . pubsub . addEventListener ( 'message' , ( evt ) => {
164
+ console . log ( `Node2 received on topic ${ evt . detail . topic } : ${ uint8ArrayToString ( evt . detail . data ) } ` ) ;
165
+ } ) ;
166
+
167
+ // For testing: Node2 periodically publishes messages on several topics.
168
+ setInterval ( ( ) => {
169
+ node2 . services . pubsub . publish ( CHAT_TOPIC , uint8ArrayFromString ( 'Hello Go & Rust!' ) )
170
+ . catch ( err => console . log ( `Error publishing to ${ CHAT_TOPIC } :` , err . message ) ) ;
171
+ } , 3000 ) ;
172
+
173
+ console . log ( 'Nodes are running and ready for robust dialing and interactions.' ) ;
174
+ }
175
+
176
+ main ( ) . catch ( err => {
177
+ console . log ( 'Main execution error:' , err . message ) ;
178
+ process . exit ( 1 ) ;
179
+ } ) ;
180
+
181
+ export class NATManager {
182
+ constructor ( node ) {
183
+ node . addEventListener ( 'self:nat:status' , ( evt ) => {
184
+ console . log ( 'NAT Status:' , evt . detail )
185
+ if ( evt . detail === 'UNSUPPORTED' ) {
186
+ console . log ( 'Enabling circuit relay as fallback' )
187
+ node . configure ( circuitRelayTransport ( { discoverRelays : 2 } ) )
188
+ }
189
+ } )
190
+ }
191
+ }
0 commit comments