From 4d4bd1942e797bd19346f9c505af6720952b360b Mon Sep 17 00:00:00 2001 From: dozyio Date: Sat, 14 Dec 2024 22:48:13 +0000 Subject: [PATCH 01/16] add bootstrap mode to go peer - enables peer exchange --- go-peer/main.go | 17 ++++++++++++++++- js-peer/src/lib/libp2p.ts | 24 ++++++++++++------------ 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/go-peer/main.go b/go-peer/main.go index 7646eb5b..75087f7c 100644 --- a/go-peer/main.go +++ b/go-peer/main.go @@ -123,6 +123,7 @@ func main() { keyPath := flag.String("tls-key-path", "", "path to the tls key file (for websockets") useLogger := flag.Bool("logger", false, "write logs to file") headless := flag.Bool("headless", false, "run without chat UI") + bootstrapper := flag.Bool("bootstrapper", false, "run as a bootstrap peer") var addrsToConnectTo stringSlice flag.Var(&addrsToConnectTo, "connect", "address to connect to (can be used multiple times)") @@ -194,8 +195,22 @@ func main() { panic(err) } + gossipSubOpts := []pubsub.Option{} + + if *bootstrapper == true { + gossipSubOpts = append(gossipSubOpts, pubsub.WithPeerExchange(true)) + + pubsub.WithFloodPublish(true) + + // See https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#recommendations-for-network-operators + pubsub.GossipSubD = 0 + pubsub.GossipSubDlo = 0 + pubsub.GossipSubDhi = 0 + pubsub.GossipSubDout = 0 + } + // create a new PubSub service using the GossipSub router - ps, err := pubsub.NewGossipSub(ctx, h) + ps, err := pubsub.NewGossipSub(ctx, h, gossipSubOpts...) if err != nil { panic(err) } diff --git a/js-peer/src/lib/libp2p.ts b/js-peer/src/lib/libp2p.ts index c1b80fc2..054c5ed2 100644 --- a/js-peer/src/lib/libp2p.ts +++ b/js-peer/src/lib/libp2p.ts @@ -100,17 +100,17 @@ export async function startLibp2p(): Promise { log(`changed multiaddrs: peer ${peer.id.toString()} multiaddrs: ${multiaddrs}`) }) - // 👇 explicitly dial peers discovered via pubsub - libp2p.addEventListener('peer:discovery', (event) => { - const { multiaddrs, id } = event.detail - - if (libp2p.getConnections(id)?.length > 0) { - log(`Already connected to peer %s. Will not try dialling`, id) - return - } - - dialWebRTCMaddrs(libp2p, multiaddrs) - }) + // // 👇 explicitly dial peers discovered via pubsub + // libp2p.addEventListener('peer:discovery', (event) => { + // const { multiaddrs, id } = event.detail + // + // if (libp2p.getConnections(id)?.length > 0) { + // log(`Already connected to peer %s. Will not try dialling`, id) + // return + // } + // + // dialWebRTCMaddrs(libp2p, multiaddrs) + // }) return libp2p } @@ -200,4 +200,4 @@ export const getFormattedConnections = (connections: Connection[]) => connections.map((conn) => ({ peerId: conn.remotePeer, protocols: [...new Set(conn.remoteAddr.protoNames())], - })) \ No newline at end of file + })) From 54612346c63a26e80a34fbe10f8d7f9fe8da5784 Mon Sep 17 00:00:00 2001 From: dozyio Date: Sat, 14 Dec 2024 22:56:15 +0000 Subject: [PATCH 02/16] fix: bool comparison --- go-peer/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-peer/main.go b/go-peer/main.go index 75087f7c..6a7a2774 100644 --- a/go-peer/main.go +++ b/go-peer/main.go @@ -197,7 +197,7 @@ func main() { gossipSubOpts := []pubsub.Option{} - if *bootstrapper == true { + if *bootstrapper { gossipSubOpts = append(gossipSubOpts, pubsub.WithPeerExchange(true)) pubsub.WithFloodPublish(true) From dbf316e5c1d5576bc5db22e8fcdf553fef3581ba Mon Sep 17 00:00:00 2001 From: dozyio Date: Mon, 16 Dec 2024 22:10:09 +0000 Subject: [PATCH 03/16] feat(go-peer): directConnection --- go-peer/main.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/go-peer/main.go b/go-peer/main.go index 6a7a2774..5394f166 100644 --- a/go-peer/main.go +++ b/go-peer/main.go @@ -125,6 +125,9 @@ func main() { headless := flag.Bool("headless", false, "run without chat UI") bootstrapper := flag.Bool("bootstrapper", false, "run as a bootstrap peer") + var directPeers stringSlice + flag.Var(&directPeers, "directpeer", "reciprocal gossipsub bootstrap peers (can be used multiple times)") + var addrsToConnectTo stringSlice flag.Var(&addrsToConnectTo, "connect", "address to connect to (can be used multiple times)") @@ -200,6 +203,11 @@ func main() { if *bootstrapper { gossipSubOpts = append(gossipSubOpts, pubsub.WithPeerExchange(true)) + if len(directPeers) > 0 { + dp := peerStrSliceToAddrInfoSlice(directPeers) + gossipSubOpts = append(gossipSubOpts, pubsub.WithDirectPeers(dp)) + } + pubsub.WithFloodPublish(true) // See https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#recommendations-for-network-operators @@ -281,6 +289,24 @@ func main() { } } +func peerStrSliceToAddrInfoSlice(peerStrs []string) []peer.AddrInfo { + var addrInfos []peer.AddrInfo + + if len(peerStrs) > 0 { + for _, addr := range peerStrs { + peerInfo, err := peer.AddrInfoFromString(addr) + if err != nil { + LogMsgf("Failed to parse multiaddr: %s", err.Error()) + continue + } + + addrInfos = append(addrInfos, *peerInfo) + } + } + + return addrInfos +} + // printErr is like fmt.Printf, but writes to stderr. func printErr(m string, args ...interface{}) { fmt.Fprintf(os.Stderr, m, args...) From 03e1772d4b136260908db4e2fc53173e272649ff Mon Sep 17 00:00:00 2001 From: dozyio Date: Thu, 19 Dec 2024 19:46:37 +0000 Subject: [PATCH 04/16] add directConnectTicks to go-peer --- go-peer/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/go-peer/main.go b/go-peer/main.go index 5394f166..16df7af9 100644 --- a/go-peer/main.go +++ b/go-peer/main.go @@ -206,6 +206,7 @@ func main() { if len(directPeers) > 0 { dp := peerStrSliceToAddrInfoSlice(directPeers) gossipSubOpts = append(gossipSubOpts, pubsub.WithDirectPeers(dp)) + pubsub.WithDirectConnectTicks(60) // attempt to reconnect to direct peers every 60 ticks (seconds) } pubsub.WithFloodPublish(true) From a57f1e29fbae54e05ac7106193975dc73a2ceca3 Mon Sep 17 00:00:00 2001 From: dozyio Date: Thu, 19 Dec 2024 19:48:31 +0000 Subject: [PATCH 05/16] chore: restore js-peer/src/lib/libp2p.ts --- js-peer/src/lib/libp2p.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/js-peer/src/lib/libp2p.ts b/js-peer/src/lib/libp2p.ts index 054c5ed2..c1b80fc2 100644 --- a/js-peer/src/lib/libp2p.ts +++ b/js-peer/src/lib/libp2p.ts @@ -100,17 +100,17 @@ export async function startLibp2p(): Promise { log(`changed multiaddrs: peer ${peer.id.toString()} multiaddrs: ${multiaddrs}`) }) - // // 👇 explicitly dial peers discovered via pubsub - // libp2p.addEventListener('peer:discovery', (event) => { - // const { multiaddrs, id } = event.detail - // - // if (libp2p.getConnections(id)?.length > 0) { - // log(`Already connected to peer %s. Will not try dialling`, id) - // return - // } - // - // dialWebRTCMaddrs(libp2p, multiaddrs) - // }) + // 👇 explicitly dial peers discovered via pubsub + libp2p.addEventListener('peer:discovery', (event) => { + const { multiaddrs, id } = event.detail + + if (libp2p.getConnections(id)?.length > 0) { + log(`Already connected to peer %s. Will not try dialling`, id) + return + } + + dialWebRTCMaddrs(libp2p, multiaddrs) + }) return libp2p } @@ -200,4 +200,4 @@ export const getFormattedConnections = (connections: Connection[]) => connections.map((conn) => ({ peerId: conn.remotePeer, protocols: [...new Set(conn.remoteAddr.protoNames())], - })) + })) \ No newline at end of file From 5b8e6505a352ff3ccb7c901d74686735a475c8a6 Mon Sep 17 00:00:00 2001 From: Daniel Norman <1992255+2color@users.noreply.github.com> Date: Wed, 26 Feb 2025 16:48:43 +0100 Subject: [PATCH 06/16] feat: deploy to ipfs from gh actions (#234) * feat: add ipfs deploy action * ci: add permissions to workflow * ci: run deploy workflow on all commits to main --------- Co-authored-by: Daniel N <2color@users.noreply.github.com> --- .github/workflows/js-peer.yml | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/js-peer.yml b/.github/workflows/js-peer.yml index ccc196ef..97c7c4d1 100644 --- a/.github/workflows/js-peer.yml +++ b/.github/workflows/js-peer.yml @@ -1,17 +1,20 @@ name: js-peer build +permissions: + contents: read + pull-requests: write + statuses: write + on: push: - branches: [main] - paths: - - 'js-peer/**' + branches: [main] # Deploy every commit so we can CIDs for every commit on main pull_request: branches: [main] paths: - - 'js-peer/**' + - 'js-peer/**' # only run for PRs changing js-peer jobs: - build-frontend: + build-and-deploy: runs-on: ubuntu-latest defaults: run: @@ -24,3 +27,11 @@ jobs: - run: npm ci - run: npm run lint - run: npm run build + - uses: ipfs/ipfs-deploy-action@v1 + name: Deploy to IPFS + id: deploy + with: + path-to-deploy: out + storacha-key: ${{ secrets.STORACHA_KEY }} + storacha-proof: ${{ secrets.STORACHA_PROOF }} + github-token: ${{ github.token }} From 1e50566d48c892aca11a894e0324a7e1774e08a7 Mon Sep 17 00:00:00 2001 From: Daniel N <2color@users.noreply.github.com> Date: Wed, 26 Feb 2025 17:37:31 +0100 Subject: [PATCH 07/16] ci: fix js-peer deployment --- .github/workflows/js-peer.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/js-peer.yml b/.github/workflows/js-peer.yml index 97c7c4d1..fe08b064 100644 --- a/.github/workflows/js-peer.yml +++ b/.github/workflows/js-peer.yml @@ -31,7 +31,8 @@ jobs: name: Deploy to IPFS id: deploy with: - path-to-deploy: out + # 👇 note that working-directory doesn't apply to action steps with `uses`, so we need to specify the full path + path-to-deploy: js-peer/out storacha-key: ${{ secrets.STORACHA_KEY }} storacha-proof: ${{ secrets.STORACHA_PROOF }} github-token: ${{ github.token }} From a5caa3df8a65771898eabab6bc223b8a01c66f26 Mon Sep 17 00:00:00 2001 From: Dave Huseby Date: Wed, 26 Feb 2025 19:48:02 -0700 Subject: [PATCH 08/16] workshop: initialize workshop book (#230) * initialize workshop mdbook Signed-off-by: Dave Grantham * Update workshop/src/introduction.md Co-authored-by: Daniel Norman <1992255+2color@users.noreply.github.com> --------- Signed-off-by: Dave Grantham Co-authored-by: Daniel Norman <1992255+2color@users.noreply.github.com> --- .github/workflows/mdbook.yml | 60 ++++++++++++++++++++++++++++++++++++ .gitignore | 2 +- workshop/.gitignore | 1 + workshop/book.toml | 6 ++++ workshop/src/SUMMARY.md | 8 +++++ workshop/src/go.md | 1 + workshop/src/introduction.md | 19 ++++++++++++ workshop/src/python.md | 1 + workshop/src/rust.md | 1 + workshop/src/typescript.md | 1 + 10 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/mdbook.yml create mode 100644 workshop/.gitignore create mode 100644 workshop/book.toml create mode 100644 workshop/src/SUMMARY.md create mode 100644 workshop/src/go.md create mode 100644 workshop/src/introduction.md create mode 100644 workshop/src/python.md create mode 100644 workshop/src/rust.md create mode 100644 workshop/src/typescript.md diff --git a/.github/workflows/mdbook.yml b/.github/workflows/mdbook.yml new file mode 100644 index 00000000..3231972f --- /dev/null +++ b/.github/workflows/mdbook.yml @@ -0,0 +1,60 @@ +# Sample workflow for building and deploying a mdBook site to GitHub Pages +# +# To get started with mdBook see: https://rust-lang.github.io/mdBook/index.html +# +name: Deploy mdBook site to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Build job + build: + runs-on: ubuntu-latest + env: + MDBOOK_VERSION: 0.4.45 + steps: + - uses: actions/checkout@v4 + - name: Install mdBook + run: | + curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf -y | sh + rustup update + cargo install --version ${MDBOOK_VERSION} mdbook + - name: Setup Pages + id: pages + uses: actions/configure-pages@v5 + - name: Build with mdBook + run: | + cd ./workshop + mdbook build + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./workshop/book + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages diff --git a/.gitignore b/.gitignore index f099dde8..8e8f5b54 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,4 @@ yarn.lock .vscode/ .DS_Store go-peer/go-peer -**/.idea \ No newline at end of file +**/.idea diff --git a/workshop/.gitignore b/workshop/.gitignore new file mode 100644 index 00000000..7585238e --- /dev/null +++ b/workshop/.gitignore @@ -0,0 +1 @@ +book diff --git a/workshop/book.toml b/workshop/book.toml new file mode 100644 index 00000000..7870a0ce --- /dev/null +++ b/workshop/book.toml @@ -0,0 +1,6 @@ +[book] +authors = ["Dave Grantham"] +language = "en" +multilingual = false +src = "src" +title = "Universal Connectivity Workshop" diff --git a/workshop/src/SUMMARY.md b/workshop/src/SUMMARY.md new file mode 100644 index 00000000..446d266c --- /dev/null +++ b/workshop/src/SUMMARY.md @@ -0,0 +1,8 @@ +# Summary + +[Introduction](./introduction.md) + +- [Typescript](./typescript.md) +- [Go](./go.md) +- [Rust](./rust.md) +- [Python](./python.md) diff --git a/workshop/src/go.md b/workshop/src/go.md new file mode 100644 index 00000000..21a27f5d --- /dev/null +++ b/workshop/src/go.md @@ -0,0 +1 @@ +# Go Universal Connectivity Workshop diff --git a/workshop/src/introduction.md b/workshop/src/introduction.md new file mode 100644 index 00000000..6026a3d5 --- /dev/null +++ b/workshop/src/introduction.md @@ -0,0 +1,19 @@ +# Introduction + +Welcome to the Universal Connectivity Workshop. This short course is intended +to familiarize you with the concepts and vocabulary of libp2p while also giving +you experience building a useful peer-to-peer application using libp2p. This +course assumes that you are familiar with software development and basic networking concepts using either +Typescript, Go, Rust, or Python. This workshop is divided up based on the +programming language. All of them cover the same material but are specialized +to each language. + +Your first task is to choose which language you would like to take this course +in and then click on the appropriate link below: + +I wish do this workshop using: +* [Typescript](./typescript.md) +* [Go](./go.md) +* [Rust](./rust.md) +* [Python](./python.md) + diff --git a/workshop/src/python.md b/workshop/src/python.md new file mode 100644 index 00000000..0eaaf68e --- /dev/null +++ b/workshop/src/python.md @@ -0,0 +1 @@ +# Python Universal Connectivity Workshop diff --git a/workshop/src/rust.md b/workshop/src/rust.md new file mode 100644 index 00000000..409e98ce --- /dev/null +++ b/workshop/src/rust.md @@ -0,0 +1 @@ +# Rust Universal Connectivity Workshop diff --git a/workshop/src/typescript.md b/workshop/src/typescript.md new file mode 100644 index 00000000..ec5c8cb2 --- /dev/null +++ b/workshop/src/typescript.md @@ -0,0 +1 @@ +# Typescript Universal Connectivity Workshop From a4a2586e5ed54d5f6f4f28dc918ca27b54a06e06 Mon Sep 17 00:00:00 2001 From: Daniel Norman <1992255+2color@users.noreply.github.com> Date: Thu, 27 Feb 2025 09:22:09 +0100 Subject: [PATCH 09/16] js-peer: overhaul ui and load that chat by default (#233) * feat: overhaul ui and load chat by default * fix: simplify nav bar and load chat on index route * fix: linting --------- Co-authored-by: Daniel N <2color@users.noreply.github.com> --- js-peer/src/components/chat-peer-list.tsx | 10 +- js-peer/src/components/chat.tsx | 79 +++++- .../src/components/connection-info-button.tsx | 20 ++ js-peer/src/components/connection-panel.tsx | 252 ++++++++++++++++++ js-peer/src/components/nav.tsx | 121 ++------- js-peer/src/components/peer-list.tsx | 12 +- js-peer/src/pages/chat.tsx | 24 -- js-peer/src/pages/index.tsx | 143 ++-------- 8 files changed, 388 insertions(+), 273 deletions(-) create mode 100644 js-peer/src/components/connection-info-button.tsx create mode 100644 js-peer/src/components/connection-panel.tsx delete mode 100644 js-peer/src/pages/chat.tsx diff --git a/js-peer/src/components/chat-peer-list.tsx b/js-peer/src/components/chat-peer-list.tsx index ed76f778..4835560d 100644 --- a/js-peer/src/components/chat-peer-list.tsx +++ b/js-peer/src/components/chat-peer-list.tsx @@ -4,7 +4,11 @@ import React, { useEffect, useState } from 'react' import type { PeerId } from '@libp2p/interface' import { PeerWrapper } from './peer' -export function ChatPeerList() { +interface ChatPeerListProps { + hideHeader?: boolean +} + +export function ChatPeerList({ hideHeader = false }: ChatPeerListProps) { const { libp2p } = useLibp2pContext() const [subscribers, setSubscribers] = useState([]) @@ -22,8 +26,8 @@ export function ChatPeerList() { return (
-

Peers

-
+ {!hideHeader &&

Peers

} +
{}
diff --git a/js-peer/src/components/chat.tsx b/js-peer/src/components/chat.tsx index 602f3882..ba7a97aa 100644 --- a/js-peer/src/components/chat.tsx +++ b/js-peer/src/components/chat.tsx @@ -7,6 +7,7 @@ import { Message } from './message' import { forComponent } from '@/lib/logger' import { ChatPeerList } from './chat-peer-list' import { ChevronLeftIcon } from '@heroicons/react/20/solid' +import { UsersIcon } from '@heroicons/react/24/outline' import Blockies from 'react-18-blockies' import { peerIdFromString } from '@libp2p/peer-id' @@ -22,6 +23,7 @@ export default function ChatContainer() { const [input, setInput] = useState('') const fileRef = useRef(null) const [messages, setMessages] = useState([]) + const [showMobilePeerList, setShowMobilePeerList] = useState(false) // Send message to public chat over gossipsub const sendPublicMessage = useCallback(async () => { @@ -183,6 +185,10 @@ export default function ChatContainer() { setMessages(messageHistory) } + const toggleMobilePeerList = () => { + setShowMobilePeerList(!showMobilePeerList) + } + useEffect(() => { // assumes a chat room is a peerId thus a direct message if (roomId === PUBLIC_CHAT_ROOM_ID) { @@ -193,26 +199,71 @@ export default function ChatContainer() { }, [roomId, directMessages, messageHistory]) return ( -
-
-
+
+
+
{roomId === PUBLIC_CHAT_ROOM_ID && ( - {PUBLIC_CHAT_ROOM_NAME} + <> + {PUBLIC_CHAT_ROOM_NAME} + + )} {roomId !== PUBLIC_CHAT_ROOM_ID && ( <> {roomId.toString().slice(-7)} - +
+ + +
)}
-
+ + {/* Show mobile peer list when toggled */} + {showMobilePeerList && ( +
+
+

Peers

+ +
+ +
+ )} + +
    {messages.map(({ msgId, msg, fileObjectUrl, peerId, read, receivedAt }: ChatMessage) => (
-
+
@@ -282,7 +333,9 @@ export default function ChatContainer() {
- +
+ +
) diff --git a/js-peer/src/components/connection-info-button.tsx b/js-peer/src/components/connection-info-button.tsx new file mode 100644 index 00000000..5cff4a0a --- /dev/null +++ b/js-peer/src/components/connection-info-button.tsx @@ -0,0 +1,20 @@ +import { ServerIcon } from '@heroicons/react/24/outline' +import React from 'react' + +interface ConnectionInfoButtonProps { + onClick: () => void +} + +export default function ConnectionInfoButton({ onClick }: ConnectionInfoButtonProps) { + return ( + + ) +} diff --git a/js-peer/src/components/connection-panel.tsx b/js-peer/src/components/connection-panel.tsx new file mode 100644 index 00000000..af798731 --- /dev/null +++ b/js-peer/src/components/connection-panel.tsx @@ -0,0 +1,252 @@ +import { useLibp2pContext } from '@/context/ctx' +import type { PeerUpdate, Connection } from '@libp2p/interface' +import { useCallback, useEffect, useState } from 'react' +import { Multiaddr, multiaddr } from '@multiformats/multiaddr' +import { connectToMultiaddr } from '../lib/libp2p' +import Spinner from '@/components/spinner' +import PeerList from '@/components/peer-list' +import { Dialog, DialogTitle, DialogBody } from '@/components/dialog' +import { + XMarkIcon, + ChevronDownIcon, + ChevronUpIcon, + ClipboardIcon, + ClipboardDocumentCheckIcon, +} from '@heroicons/react/24/outline' + +export default function ConnectionPanel({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) { + const { libp2p } = useLibp2pContext() + const [connections, setConnections] = useState([]) + const [listenAddresses, setListenAddresses] = useState([]) + const [maddr, setMultiaddr] = useState('') + const [dialling, setDialling] = useState(false) + const [err, setErr] = useState('') + const [addressesExpanded, setAddressesExpanded] = useState(true) + const [connectionsExpanded, setConnectionsExpanded] = useState(true) + const [copiedAddress, setCopiedAddress] = useState(null) + const [copiedPeerId, setCopiedPeerId] = useState(false) + + 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:close', onConnection) + } + }, [libp2p, setConnections]) + + useEffect(() => { + const onPeerUpdate = (evt: CustomEvent) => { + const maddrs = evt.detail.peer.addresses?.map((p) => p.multiaddr) + setListenAddresses(maddrs ?? []) + } + libp2p.addEventListener('self:peer:update', onPeerUpdate) + + return () => { + libp2p.removeEventListener('self:peer:update', onPeerUpdate) + } + }, [libp2p, setListenAddresses]) + + const handleConnectToMultiaddr = useCallback( + async (e: React.MouseEvent) => { + setErr('') + if (!maddr) { + return + } + setDialling(true) + try { + await connectToMultiaddr(libp2p)(multiaddr(maddr)) + } catch (e: any) { + setErr(e?.message ?? 'Error connecting') + } finally { + setDialling(false) + } + }, + [libp2p, maddr], + ) + + const handleMultiaddrChange = useCallback( + (e: React.ChangeEvent) => { + setMultiaddr(e.target.value) + }, + [setMultiaddr], + ) + + const toggleAddresses = () => { + setAddressesExpanded(!addressesExpanded) + } + + const toggleConnections = () => { + setConnectionsExpanded(!connectionsExpanded) + } + + const copyAddress = (index: number, address: string) => { + navigator.clipboard.writeText(address).then(() => { + setCopiedAddress(index) + setTimeout(() => setCopiedAddress(null), 2000) + }) + } + + const copyPeerId = () => { + navigator.clipboard.writeText(libp2p.peerId.toString()).then(() => { + setCopiedPeerId(true) + setTimeout(() => setCopiedPeerId(false), 2000) + }) + } + + return ( + +
+ Connection Information + +
+ +
+
+

This PeerID:

+
+

{libp2p.peerId.toString()}

+ +
+
+ +
+
+

Addresses ({listenAddresses.length}):

+ +
+ {addressesExpanded && ( +
+ {listenAddresses.length === 0 ? ( +

No addresses available

+ ) : ( +
    + {listenAddresses.map((ma, index) => ( +
  • + {ma.toString()} + +
  • + ))} +
+ )} +
+ )} + {!addressesExpanded && listenAddresses.length > 0 && ( +

Click to show {listenAddresses.length} addresses

+ )} +
+ +
+ +
+ +
+ + {err &&

{err}

} +
+ + {connections.length > 0 && ( +
+
+

Connections ({connections.length}):

+ +
+ {connectionsExpanded && ( +
+ +
+ )} + {!connectionsExpanded && ( +

Click to show {connections.length} connections

+ )} +
+ )} +
+
+
+ ) +} diff --git a/js-peer/src/components/nav.tsx b/js-peer/src/components/nav.tsx index 8441dcc0..e4e221f1 100644 --- a/js-peer/src/components/nav.tsx +++ b/js-peer/src/components/nav.tsx @@ -1,26 +1,17 @@ import { Fragment } from 'react' -import { Disclosure, Menu, Transition } from '@headlessui/react' +import { Disclosure, DisclosureButton, DisclosurePanel, Menu, Transition } from '@headlessui/react' import { Bars3Icon, BellIcon, XMarkIcon } from '@heroicons/react/24/outline' import Link from 'next/link' import Image from 'next/image' import { useRouter } from 'next/router' -const navigationItems = [ - { name: 'Connecting to a Peer', href: '/' }, - { name: 'Chat', href: '/chat' }, - { name: 'Source', href: 'https://github.com/libp2p/universal-connectivity' }, -] -const userNavigation = [ - { name: 'Your Profile', href: '#' }, - { name: 'Settings', href: '#' }, - { name: 'Sign out', href: '#' }, -] +const navigationItems = [{ name: 'Source', href: 'https://github.com/libp2p/universal-connectivity' }] function classNames(...classes: string[]) { return classes.filter(Boolean).join(' ') } -export default function Navigation() { +export default function Navigation({ connectionInfoButton }: { connectionInfoButton?: React.ReactNode }) { const router = useRouter() return ( @@ -28,12 +19,25 @@ export default function Navigation() { {({ open }) => ( <>
-
-
+
+
libp2p logo +
+

Universal Connectivity

+ libp2p hero +
-
+
+ + - -
- {/* Mobile menu button */} - - Open main menu - {open ? ( - +
{connectionInfoButton}
- - -
- {navigationItems.map((item) => ( - - - {item.name} - - - ))} -
-
)} diff --git a/js-peer/src/components/peer-list.tsx b/js-peer/src/components/peer-list.tsx index 05342b83..cdd5deb8 100644 --- a/js-peer/src/components/peer-list.tsx +++ b/js-peer/src/components/peer-list.tsx @@ -41,7 +41,7 @@ function Peer({ connection }: PeerProps) { return (
  • -
    +
    @@ -59,14 +59,14 @@ function Peer({ connection }: PeerProps) {
    - {/*
    */} -
    +
  • diff --git a/js-peer/src/pages/chat.tsx b/js-peer/src/pages/chat.tsx deleted file mode 100644 index ef7bfec1..00000000 --- a/js-peer/src/pages/chat.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import Head from 'next/head' -import Nav from '@/components/nav' -import ChatContainer from '@/components/chat' - -export default function Chat() { - return ( - <> - - js-libp2p Chat - - - - -
    -
    - - ) -} diff --git a/js-peer/src/pages/index.tsx b/js-peer/src/pages/index.tsx index 478842e1..2f9a0db9 100644 --- a/js-peer/src/pages/index.tsx +++ b/js-peer/src/pages/index.tsx @@ -1,72 +1,17 @@ import Head from 'next/head' import Nav from '@/components/nav' +import ChatContainer from '@/components/chat' +import ConnectionPanel from '@/components/connection-panel' +import { useState } from 'react' import { useLibp2pContext } from '@/context/ctx' -import type { PeerUpdate, Connection } 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 ConnectionInfoButton from '@/components/connection-info-button' -export default function Home() { - const { libp2p } = useLibp2pContext() - const [connections, setConnections] = useState([]) - const [listenAddresses, setListenAddresses] = useState([]) - const [maddr, setMultiaddr] = useState('') - const [dialling, setDialling] = useState(false) - const [err, setErr] = useState('') +export default function Chat() { + const [isConnectionPanelOpen, setIsConnectionPanelOpen] = useState(false) - 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) => { - const maddrs = evt.detail.peer.addresses?.map((p) => p.multiaddr) - setListenAddresses(maddrs ?? []) - } - libp2p.addEventListener('self:peer:update', onPeerUpdate) - - return () => { - libp2p.removeEventListener('self:peer:update', onPeerUpdate) - } - }, [libp2p, setListenAddresses]) - - const handleConnectToMultiaddr = useCallback( - async (e: React.MouseEvent) => { - setErr('') - if (!maddr) { - return - } - setDialling(true) - try { - await connectToMultiaddr(libp2p)(multiaddr(maddr)) - } catch (e: any) { - setErr(e?.message ?? 'Error connecting') - } finally { - setDialling(false) - } - }, - [libp2p, maddr], - ) - - const handleMultiaddrChange = useCallback( - (e: React.ChangeEvent) => { - setMultiaddr(e.target.value) - }, - [setMultiaddr], - ) + const handleOpenConnectionPanel = () => { + setIsConnectionPanelOpen(true) + } return ( <> @@ -76,71 +21,15 @@ export default function Home() { -
    -