Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
162a99c
added-multiaddrs
Nkovaturient Jan 29, 2025
7b7c596
connectPeerByID-initial-fix
Nkovaturient Feb 3, 2025
0b97a97
modified-kadDHT-with-peerRouting-method
Nkovaturient Feb 6, 2025
322e072
Merge branch 'main' into connect-via-peerId
Nkovaturient Feb 6, 2025
6aef49c
prettier-code-styling
Nkovaturient Feb 6, 2025
ae5b517
add-peer-connectivity&validity
Nkovaturient Feb 6, 2025
3c20c29
Merge remote-tracking branch 'origin/main' into connect-via-peerId
2color Feb 12, 2025
e82651d
chore: normalize linebreaks
2color Feb 12, 2025
99b9826
chore:improve-peerID-connection-logic
Nkovaturient Feb 18, 2025
2ff31ee
Merge branch 'libp2p:main' into connect-via-peerId
Nkovaturient Feb 18, 2025
3dc0459
Merge branch 'main' of https://github.com/Nkovaturient/universal-conn…
Nkovaturient Feb 18, 2025
a0d5c28
Merge branch 'connect-via-peerId' of https://github.com/Nkovaturient/…
Nkovaturient Feb 18, 2025
18a31fb
Merge branch 'main' into connect-via-peerId
Nkovaturient Feb 21, 2025
c459ded
Refactor: Unify multiaddr and PeerID connection in index.tsx & run fo…
Nkovaturient Feb 21, 2025
a2cda72
Merge branch 'main' of https://github.com/Nkovaturient/universal-conn…
Nkovaturient Feb 21, 2025
eb137d3
resolve-merge-conflicts
Nkovaturient Feb 21, 2025
b5a69c3
removed-setConn-ctx+apt-peer-list-style
Nkovaturient Feb 27, 2025
94b2ed6
remove-setConn-ctx+revert-peer-list-styling
Nkovaturient Feb 27, 2025
1acb6a4
Merge branch 'libp2p:main' into connect-via-peerId
Nkovaturient Mar 2, 2025
d8c5d07
afresh-dial-peerID-ability
Nkovaturient Mar 13, 2025
ddc386d
Merge branch 'main' of https://github.com/Nkovaturient/universal-conn…
Nkovaturient Mar 13, 2025
a0314d8
Merge branch 'connect-via-peerId' of https://github.com/Nkovaturient/…
Nkovaturient Mar 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 25 additions & 30 deletions js-peer/src/components/peer-list.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { XCircleIcon } from '@heroicons/react/24/solid'
import type { PeerId, Connection } from '@libp2p/interface'
import { Badge } from './badge'
import { useCallback } from 'react'
import { useLibp2pContext } from '@/context/ctx'


interface PeerListProps {
connections: Connection[]
}
Expand All @@ -30,45 +30,40 @@ function Peer({ connection }: PeerProps) {
},
[libp2p],
)

let ipAddr
try {
const nodeAddr = connection.remoteAddr?.nodeAddress()
ipAddr = `${nodeAddr.address}:${nodeAddr.port} |`
} catch (e) {
} catch(e) {
ipAddr = null
}

return (
<li key={connection.id} className="flex justify-between gap-x-6 py-3">
<div className="flex min-w-0 gap-x-4">
<div className="mt-1 flex items-center gap-x-1.5">
<div className="flex-none rounded-full bg-emerald-500/20 p-1">
<div className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
</div>
</div>
{/* <img className="h-12 w-12 flex-none rounded-full bg-gray-50" src={person.imageUrl} alt="" /> */}
<div className="min-w-0 flex-auto">
<p className="text-sm font-semibold leading-6 text-gray-900">
{connection.remotePeer.toString()}{' '}
{connection.remoteAddr.protoNames().includes('webrtc') ? <Badge color="indigo">P2P Browser</Badge> : null}
</p>
<p className="mt-1 truncate text-xs leading-5 text-gray-500">
{ipAddr} {connection.remoteAddr.protoNames().join(', ')}
</p>
<li key={connection.id} className="flex justify-between flex-wrap mx-2 py-2 gap-x-6 py-5">
<div className="flex min-w-0 gap-x-4">
<div className="mt-1 flex items-center gap-x-1.5">
<div className="flex-none rounded-full bg-emerald-500/20 p-1">
<div className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
</div>
</div>

{/* <div className="flex gap-x-2 items-center "> */}
<div className="hidden sm:flex sm:flex-col sm:items-end">
<button
onClick={() => handleDisconnectPeer(connection.remotePeer)}
className="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded flex flex-row"
>
<XCircleIcon className="w-6 h-6" />
<span className="pl-1">Disconnect</span>
</button>
<div className="min-w-0 flex-auto">
<p className="text-sm font-semibold leading-6 text-gray-900">{connection.remotePeer.toString()}</p>
<p className="mt-1 truncate text-xs leading-5 text-gray-500">{connection.remoteAddr.toString()}</p>
<p className="mt-1 text-xs leading-5 text-gray-500">
Connected: {new Date(connection.timeline.open).toLocaleString()}
</p>
</div>
</li>
</div>
<div className="hidden sm:flex sm:flex-col sm:items-end">
<button
onClick={() => handleDisconnectPeer(connection.remotePeer)}
className="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded flex flex-row"
>
<XCircleIcon className="w-6 h-6" />
<span className="pl-1">Disconnect</span>
</button>
</div>
</li>
)
}
82 changes: 82 additions & 0 deletions js-peer/src/components/peerID-connect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { useState } from 'react'
import { useLibp2pContext } from '@/context/ctx'
import { peerIdFromString } from '@libp2p/peer-id'
import Spinner from './spinner'

export default function PeerIDConnect() {
const { libp2p } = useLibp2pContext()
const [peerIdInput, setPeerIdInput] = useState('')
const [error, setError] = useState<string | null>(null)
const [loading, setLoading] = useState(false)

const handleConnect = async () => {
setError(null)

if (!peerIdInput.trim()) {
setError('❌ Please enter a valid PeerID')
return
}

if (!libp2p) {
setError('❌ Libp2p instance not found')
return
}

setLoading(true)
try {
// Parse the peer ID string
const peerId = peerIdFromString(peerIdInput.trim())

// Use libp2p.dial() which handles peer routing internally
await libp2p.dial(peerId)

setPeerIdInput('')
} catch (e) {
const errorMessage = e instanceof Error ? e.message : String(e)
setError(`❌ Error connecting to peer: ${errorMessage}`)
} finally {
setLoading(false)
}
}

return (
<div className="my-6 w-full max-w-2xl">
<div className="bg-white p-4 rounded-lg shadow-sm border border-gray-200">
<label htmlFor="peer-cid" className="block text-sm font-medium text-gray-900 mb-2">
Connect to Peer by PeerID
</label>
<div className="flex gap-2">
<input
value={peerIdInput}
type="text"
name="peer-cid"
id="peer-cid"
className="flex-1 rounded-md border-0 py-1.5 px-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-indigo-600"
placeholder="Enter PeerID (e.g., 12D3Koo...)"
onChange={(e) => setPeerIdInput(e.target.value)}
disabled={loading}
/>
<button
type="button"
className={`inline-flex items-center px-4 py-2 rounded-md
${loading ? 'bg-indigo-400 cursor-not-allowed' : 'bg-indigo-600 hover:bg-indigo-500'}
text-white font-semibold text-sm transition-colors`}
onClick={handleConnect}
disabled={loading}
>
{loading ? (
<>
<Spinner />
Connecting...
</>
) : (
'Connect to PeerID'
)}
</button>
</div>

{error && <div className="mt-2 text-sm text-red-600">{error}</div>}
</div>
</div>
)
}
28 changes: 25 additions & 3 deletions js-peer/src/context/ctx.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'
import { startLibp2p } from '../lib/libp2p'
import { ChatProvider } from './chat-ctx'
import type { Libp2p, PubSub } from '@libp2p/interface'
import type { Libp2p, PubSub, Connection } from '@libp2p/interface'
import type { Identify } from '@libp2p/identify'
import type { DirectMessage } from '@/lib/direct-message'
import type { DelegatedRoutingV1HttpApiClient } from '@helia/delegated-routing-v1-http-api-client'
Expand All @@ -14,9 +14,15 @@ export type Libp2pType = Libp2p<{
delegatedRouting: DelegatedRoutingV1HttpApiClient
}>

export const libp2pContext = createContext<{ libp2p: Libp2pType }>({
export const libp2pContext = createContext<{
libp2p: Libp2pType
connections: Connection[]
setConnections: (connections: Connection[]) => void
}>({
// @ts-ignore to avoid having to check isn't undefined everywhere. Can't be undefined because children are conditionally rendered
libp2p: undefined,
connections: [],
setConnections: () => {},
})

interface WrapperProps {
Expand All @@ -28,6 +34,22 @@ let loaded = false
export function AppWrapper({ children }: WrapperProps) {
const [libp2p, setLibp2p] = useState<Libp2pType | undefined>(undefined)
const [error, setError] = useState('')
const [connections, setConnections] = useState<Connection[]>([])

useEffect(() => {
if(!libp2p) return
const onConnection = () => {
const connections = libp2p.getConnections()
setConnections(connections)
}
onConnection()
libp2p.addEventListener('connection:open', onConnection)
libp2p.addEventListener('connection:close', onConnection)
return () => {
libp2p.removeEventListener('connection:open', onConnection)
libp2p.removeEventListener('connection:close', onConnection)
}
}, [libp2p, setConnections])

useEffect(() => {
const init = async () => {
Expand Down Expand Up @@ -57,7 +79,7 @@ export function AppWrapper({ children }: WrapperProps) {
}

return (
<libp2pContext.Provider value={{ libp2p }}>
<libp2pContext.Provider value={{ libp2p, connections, setConnections }}>
<ChatProvider>{children}</ChatProvider>
</libp2pContext.Provider>
)
Expand Down
3 changes: 2 additions & 1 deletion js-peer/src/lib/libp2p.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export async function startLibp2p(): Promise<Libp2pType> {
bootstrap({
// The app-specific bootstrappers that use WebTransport and WebRTC-direct and have ephemeral multiadrrs
// that are resolved above using the delegated routing API
list: bootstrapAddrs,
list: bootstrapAddrs
}),
],
services: {
Expand Down Expand Up @@ -114,6 +114,7 @@ export async function startLibp2p(): Promise<Libp2pType> {
return libp2p
}


// message IDs are used to dedupe inbound messages
// every agent in network should use the same message id function
// messages could be perceived as duplicate if this isnt added (as opposed to rust peer which has unique message ids)
Expand Down
20 changes: 4 additions & 16 deletions js-peer/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,22 @@
import Head from 'next/head'
import Nav from '@/components/nav'
import { useLibp2pContext } from '@/context/ctx'
import type { PeerUpdate, Connection } from '@libp2p/interface'
import type { PeerUpdate } from '@libp2p/interface'
import { useCallback, useEffect, useState } from 'react'
import Image from 'next/image'
import { Multiaddr, multiaddr } from '@multiformats/multiaddr'
import { connectToMultiaddr } from '../lib/libp2p'
import Spinner from '@/components/spinner'
import PeerList from '@/components/peer-list'
import PeerIDConnect from '@/components/peerID-connect'

export default function Home() {
const { libp2p } = useLibp2pContext()
const [connections, setConnections] = useState<Connection[]>([])
const { libp2p, connections } = useLibp2pContext()
const [listenAddresses, setListenAddresses] = useState<Multiaddr[]>([])
const [maddr, setMultiaddr] = useState('')
const [dialling, setDialling] = useState(false)
const [err, setErr] = useState('')

useEffect(() => {
const onConnection = () => {
const connections = libp2p.getConnections()
setConnections(connections)
}
onConnection()
libp2p.addEventListener('connection:open', onConnection)
libp2p.addEventListener('connection:close', onConnection)
return () => {
libp2p.removeEventListener('connection:open', onConnection)
libp2p.removeEventListener('connection:clone', onConnection)
}
}, [libp2p, setConnections])

useEffect(() => {
const onPeerUpdate = (evt: CustomEvent<PeerUpdate>) => {
Expand Down Expand Up @@ -129,6 +116,7 @@ export default function Home() {
</button>
{err && <p className="text-red-500">{err}</p>}
</div>
<PeerIDConnect />
<div>
{connections.length > 0 ? (
<>
Expand Down
Loading