-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add WebRTC private to private example (#173)
Restores old example of WebRTC between two browsers
- Loading branch information
1 parent
83dd9df
commit ca38ae1
Showing
10 changed files
with
562 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
...es/js-libp2p-example-webrtc-private-to-private/.github/pull_request_template.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# ⚠️ IMPORTANT ⚠️ | ||
|
||
# Please do not create a Pull Request for this repository | ||
|
||
The contents of this repository are automatically synced from the parent [js-libp2p Examples Project](https://github.com/libp2p/js-libp2p-examples) so any changes made to the standalone repository will be lost after the next sync. | ||
|
||
Please open a PR against [js-libp2p Examples](https://github.com/libp2p/js-libp2p-examples) instead. | ||
|
||
## Contributing | ||
|
||
Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. | ||
|
||
1. Fork the [js-libp2p Examples Project](https://github.com/libp2p/js-libp2p-examples) | ||
2. Create your Feature Branch (`git checkout -b feature/amazing-example`) | ||
3. Commit your Changes (`git commit -a -m 'feat: add some amazing example'`) | ||
4. Push to the Branch (`git push origin feature/amazing-example`) | ||
5. Open a Pull Request |
19 changes: 19 additions & 0 deletions
19
examples/js-libp2p-example-webrtc-private-to-private/.github/workflows/sync.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
name: pull | ||
|
||
on: | ||
workflow_dispatch | ||
|
||
jobs: | ||
sync: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- name: Pull from another repository | ||
uses: ipfs-examples/actions-pull-directory-from-repo@main | ||
with: | ||
source-repo: libp2p/js-libp2p-examples | ||
source-folder-path: examples/${{ github.event.repository.name }} | ||
source-branch: main | ||
target-branch: main | ||
git-username: github-actions | ||
git-email: github-actions@github.com |
95 changes: 95 additions & 0 deletions
95
examples/js-libp2p-example-webrtc-private-to-private/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
# @libp2p/example-webrtc-private-to-private | ||
|
||
[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) | ||
[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) | ||
[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p-examples.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-examples) | ||
[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p-examples/ci.yml?branch=main\&style=flat-square)](https://github.com/libp2p/js-libp2p-examples/actions/workflows/ci.yml?query=branch%3Amain) | ||
|
||
In libp2p terms a "private" node is one behind a [NAT firewall](https://en.wikipedia.org/wiki/Network_address_translation) that prevents it from being dialed externally. | ||
|
||
This could be a browser, a node.js process or something else. | ||
|
||
Nodes that support the [libp2p WebRTC transport](https://github.com/libp2p/specs/blob/master/webrtc/webrtc.md) such as browsers can by dialed via this method even if they are behind a NAT. | ||
|
||
When establishing a WebRTC connection, the two browsers must first exchange a series of messages that establish the required capabilities of the nodes (we only require RTC data channels, no video or audio), and their internet-facing addresses/ports. | ||
|
||
This is referred to as the "SDP handshake". The WebRTC spec requires this to take place out-of-band, so libp2p performs the handshake via a [Circuit Relay Server](https://docs.libp2p.io/concepts/nat/circuit-relay/) - this is another network node that has made some resources available for the good of the network. | ||
|
||
When two browsers dial each other the following steps occur: | ||
|
||
1. The listener makes a reservation on a relay with a free slot | ||
2. The dialer obtains the listener's relay address | ||
3. The dialer dials the relay and specifies the listeners PeerId as part of the Circuit Relay HOP protocol | ||
4. The relay opens a stream on the listener as part of the Circuit Relay STOP protocol | ||
5. A virtual connection is created between the dialer and the listener via the relay | ||
6. The dialer opens a stream on the virtual connection to perform the SDP handshake | ||
7. SDP messages are exchanged | ||
8. A direct WebRTC connection is opened between the two browsers | ||
|
||
At this point the browsers are directly connected and the relay plays no further part. | ||
|
||
## Running the Example | ||
|
||
### Build the `@libp2p/example-webrtc-private-to-private` package | ||
|
||
Build example by calling `npm i && npm run build` in the repository root. | ||
|
||
### Running the Relay Server | ||
|
||
For browsers to communicate, we first need to run a relay server: | ||
|
||
```shell | ||
npm run relay | ||
``` | ||
|
||
The [multiaddress](https://docs.libp2p.io/concepts/fundamentals/addressing/) the relay is listening on will be printed to the console. Copy one of them to your clipboard. | ||
|
||
### Running the Clients | ||
|
||
In a separate console tab, start the web server: | ||
|
||
```shell | ||
npm start | ||
``` | ||
|
||
A browser window will automatically open. Let's call this `Browser A`. | ||
|
||
Using the copied multiaddrs from the relay server, paste it into the `Remote MultiAddress` input and click the `Connect` button. | ||
`Browser A` is now connected to the relay server. | ||
|
||
Copy the multiaddr located after the `Listening on` message. | ||
|
||
Now open a second tab with the url `http://localhost:5173/`, perhaps in a different browser or a private window. Let's call this `Browser B`. | ||
|
||
Using the copied multiaddress from `Listening on` section in `Browser A`, paste it into the `Remote MultiAddress` input and click the `Connect` button. | ||
|
||
The peers are now connected to each other. | ||
|
||
Enter a message and click the `Send` button in either/both browsers and see the echo'd messages. | ||
|
||
The output should look like: | ||
|
||
`Browser A` | ||
```text | ||
Dialing '/ip4/127.0.0.1/tcp/57708/ws/p2p/12D3KooWRqAUEzPwKMoGstpfJVqr3aoinwKVPu4DLo9nQncbnuLk' | ||
Listening on /ip4/127.0.0.1/tcp/57708/ws/p2p/12D3KooWRqAUEzPwKMoGstpfJVqr3aoinwKVPu4DLo9nQncbnuLk/p2p-circuit/p2p/12D3KooW9wFiWFELqGJTbzEwtByXsPiHJdHB8n7Kin71VMYyERmC/p2p-circuit/webrtc/p2p/12D3KooW9wFiWFELqGJTbzEwtByXsPiHJdHB8n7Kin71VMYyERmC | ||
Dialing '/ip4/127.0.0.1/tcp/57708/ws/p2p/12D3KooWRqAUEzPwKMoGstpfJVqr3aoinwKVPu4DLo9nQncbnuLk/p2p-circuit/p2p/12D3KooWBZyVLJfQkofqLK4op9TPkHuUumCZt1ybQrPvNm7TVQV9/p2p-circuit/webrtc/p2p/12D3KooWBZyVLJfQkofqLK4op9TPkHuUumCZt1ybQrPvNm7TVQV9' | ||
Sending message 'helloa' | ||
Received message 'helloa' | ||
Received message 'hellob' | ||
``` | ||
|
||
`Browser B` | ||
```text | ||
Dialing '/ip4/127.0.0.1/tcp/57708/ws/p2p/12D3KooWRqAUEzPwKMoGstpfJVqr3aoinwKVPu4DLo9nQncbnuLk/p2p-circuit/p2p/12D3KooW9wFiWFELqGJTbzEwtByXsPiHJdHB8n7Kin71VMYyERmC/p2p-circuit/webrtc/p2p/12D3KooW9wFiWFELqGJTbzEwtByXsPiHJdHB8n7Kin71VMYyERmC' | ||
Listening on /ip4/127.0.0.1/tcp/57708/ws/p2p/12D3KooWRqAUEzPwKMoGstpfJVqr3aoinwKVPu4DLo9nQncbnuLk/p2p-circuit/p2p/12D3KooWBZyVLJfQkofqLK4op9TPkHuUumCZt1ybQrPvNm7TVQV9/p2p-circuit/webrtc/p2p/12D3KooWBZyVLJfQkofqLK4op9TPkHuUumCZt1ybQrPvNm7TVQV9 | ||
Received message 'helloa' | ||
Sending message 'hellob' | ||
Received message 'hellob' | ||
``` | ||
|
||
## Next steps | ||
|
||
The WebRTC transport is not limited to browsers. | ||
|
||
Why don't you try to create a Node.js version of the [browser peer script](./index.js)? |
50 changes: 50 additions & 0 deletions
50
examples/js-libp2p-example-webrtc-private-to-private/index.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>js-libp2p WebRTC</title> | ||
<style> | ||
label, | ||
button { | ||
display: block; | ||
font-weight: bold; | ||
margin: 5px 0; | ||
} | ||
div { | ||
margin-bottom: 20px; | ||
} | ||
#send-section { | ||
display: none; | ||
} | ||
input[type="text"] { | ||
width: 800px; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<div id="app"> | ||
<div> | ||
<label for="peer">Remote MultiAddress:</label> | ||
<input type="text" id="peer" /> | ||
<button id="connect">Connect</button> | ||
</div> | ||
<div id="send-section"> | ||
<label for="message">Message:</label> | ||
<input type="text" id="message" value="hello" /> | ||
<button id="send">Send</button> | ||
</div> | ||
<div id="connectionsWrapper"> | ||
<h3>Active Connections:</h3> | ||
<ul id="connections"></ul> | ||
</div> | ||
<div id="listeningAddressesWrapper"> | ||
<h3>Listening addresses:</h3> | ||
<ul id="multiaddrs"></ul> | ||
</div> | ||
<h3>Output:</h3> | ||
<div id="output"></div> | ||
</div> | ||
<script type="module" src="./index.js"></script> | ||
</body> | ||
</html> |
173 changes: 173 additions & 0 deletions
173
examples/js-libp2p-example-webrtc-private-to-private/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
import { noise } from '@chainsafe/libp2p-noise' | ||
import { yamux } from '@chainsafe/libp2p-yamux' | ||
import { circuitRelayTransport } from '@libp2p/circuit-relay-v2' | ||
import { identify, identifyPush } from '@libp2p/identify' | ||
import { ping } from '@libp2p/ping' | ||
import { webRTC } from '@libp2p/webrtc' | ||
import { webSockets } from '@libp2p/websockets' | ||
import * as filters from '@libp2p/websockets/filters' | ||
import { multiaddr, protocols } from '@multiformats/multiaddr' | ||
import { byteStream } from 'it-byte-stream' | ||
import { createLibp2p } from 'libp2p' | ||
import { fromString, toString } from 'uint8arrays' | ||
|
||
const WEBRTC_CODE = protocols('webrtc').code | ||
|
||
const output = document.getElementById('output') | ||
const sendSection = document.getElementById('send-section') | ||
const appendOutput = (line) => { | ||
const div = document.createElement('div') | ||
div.appendChild(document.createTextNode(line)) | ||
output.append(div) | ||
} | ||
const CHAT_PROTOCOL = '/libp2p/examples/chat/1.0.0' | ||
let ma | ||
let chatStream | ||
|
||
const node = await createLibp2p({ | ||
addresses: { | ||
listen: [ | ||
'/webrtc' | ||
] | ||
}, | ||
transports: [ | ||
webSockets({ | ||
filter: filters.all | ||
}), | ||
webRTC(), | ||
circuitRelayTransport({ | ||
discoverRelays: 1 | ||
}) | ||
], | ||
connectionEncrypters: [noise()], | ||
streamMuxers: [yamux()], | ||
connectionGater: { | ||
denyDialMultiaddr: () => { | ||
// by default we refuse to dial local addresses from the browser since they | ||
// are usually sent by remote peers broadcasting undialable multiaddrs but | ||
// here we are explicitly connecting to a local node so do not deny dialing | ||
// any discovered address | ||
return false | ||
} | ||
}, | ||
services: { | ||
identify: identify(), | ||
identifyPush: identifyPush(), | ||
ping: ping() | ||
} | ||
}) | ||
|
||
await node.start() | ||
|
||
function updateConnList () { | ||
// Update connections list | ||
const connListEls = node.getConnections() | ||
.map((connection) => { | ||
if (connection.remoteAddr.protoCodes().includes(WEBRTC_CODE)) { | ||
ma = connection.remoteAddr | ||
sendSection.style.display = 'block' | ||
} | ||
|
||
const el = document.createElement('li') | ||
el.textContent = connection.remoteAddr.toString() | ||
return el | ||
}) | ||
document.getElementById('connections').replaceChildren(...connListEls) | ||
} | ||
|
||
node.addEventListener('connection:open', (event) => { | ||
updateConnList() | ||
}) | ||
node.addEventListener('connection:close', (event) => { | ||
updateConnList() | ||
}) | ||
|
||
node.addEventListener('self:peer:update', (event) => { | ||
// Update multiaddrs list, only show WebRTC addresses | ||
const multiaddrs = node.getMultiaddrs() | ||
.filter(ma => isWebrtc(ma)) | ||
.map((ma) => { | ||
const el = document.createElement('li') | ||
el.textContent = ma.toString() | ||
return el | ||
}) | ||
document.getElementById('multiaddrs').replaceChildren(...multiaddrs) | ||
}) | ||
|
||
node.handle(CHAT_PROTOCOL, async ({ stream }) => { | ||
chatStream = byteStream(stream) | ||
|
||
while (true) { | ||
const buf = await chatStream.read() | ||
appendOutput(`Received message '${toString(buf.subarray())}'`) | ||
} | ||
}) | ||
|
||
const isWebrtc = (ma) => { | ||
return ma.protoCodes().includes(WEBRTC_CODE) | ||
} | ||
|
||
window.connect.onclick = async () => { | ||
ma = multiaddr(window.peer.value) | ||
appendOutput(`Dialing '${ma}'`) | ||
|
||
const signal = AbortSignal.timeout(5000) | ||
|
||
try { | ||
if (isWebrtc(ma)) { | ||
const rtt = await node.services.ping.ping(ma, { | ||
signal | ||
}) | ||
appendOutput(`Connected to '${ma}'`) | ||
appendOutput(`RTT to ${ma.getPeerId()} was ${rtt}ms`) | ||
} else { | ||
await node.dial(ma, { | ||
signal | ||
}) | ||
appendOutput('Connected to relay') | ||
} | ||
} catch (err) { | ||
if (signal.aborted) { | ||
appendOutput(`Timed out connecting to '${ma}'`) | ||
} else { | ||
appendOutput(`Connecting to '${ma}' failed - ${err.message}`) | ||
} | ||
} | ||
} | ||
|
||
window.send.onclick = async () => { | ||
if (chatStream == null) { | ||
appendOutput('Opening chat stream') | ||
|
||
const signal = AbortSignal.timeout(5000) | ||
|
||
try { | ||
const stream = await node.dialProtocol(ma, CHAT_PROTOCOL, { | ||
signal | ||
}) | ||
chatStream = byteStream(stream) | ||
|
||
Promise.resolve().then(async () => { | ||
while (true) { | ||
const buf = await chatStream.read() | ||
appendOutput(`Received message '${toString(buf.subarray())}'`) | ||
} | ||
}) | ||
} catch (err) { | ||
if (signal.aborted) { | ||
appendOutput('Timed out opening chat stream') | ||
} else { | ||
appendOutput(`Opening chat stream failed - ${err.message}`) | ||
} | ||
|
||
return | ||
} | ||
} | ||
|
||
const message = window.message.value.toString().trim() | ||
appendOutput(`Sending message '${message}'`) | ||
chatStream.write(fromString(message)) | ||
.catch(err => { | ||
appendOutput(`Error sending message - ${err.message}`) | ||
}) | ||
} |
Oops, something went wrong.