-
Notifications
You must be signed in to change notification settings - Fork 16
feat: Browser to Browser #90
Changes from 47 commits
07b5011
cf7c2bd
f2fc6e3
eefde36
3abb661
e50dd76
84c5b3c
5e3d29e
43246b4
6472150
83cd254
f786bdb
5a08ed7
0ba969c
e1bf7de
eafad87
40d72fb
c24fb7e
daed656
df5ce02
20e7c28
8ea980b
69c4208
d622b27
ddad046
602adc9
c8c2888
41c8e2d
a031dca
40fd90c
d4a6858
0b6a80f
6e8b3f6
32aa877
d167645
55c4f7b
e7a2623
45f47f2
05c783d
eb64c85
9d831a8
08575d4
f2df558
a8efbfe
0a9f5d3
a86eb4b
497e7e2
57ad032
c30664b
257101e
489876b
256fc95
7b9e98f
57779d4
2184275
2f77031
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
# js-libp2p-webrtc Browser to Browser | ||
|
||
This example leverages the [vite bundler](https://vitejs.dev/) to compile and serve the libp2p code in the browser. You can use other bundlers such as Webpack, but we will not be covering them here. | ||
|
||
## Build the `@libp2p/webrtc` package | ||
|
||
Build the `@libp2p/webrtc` package by calling `npm i && npm run build` in the repository root. | ||
|
||
## Running the Relay Server | ||
|
||
For browsers to communicate, we first need to run the LibP2P relay server: | ||
|
||
```shell | ||
npm run relay | ||
``` | ||
|
||
Copy one of the multiaddresses in the output. | ||
|
||
## Running the Example | ||
|
||
ckousik marked this conversation as resolved.
Show resolved
Hide resolved
|
||
In a separate console tab, install dependencies and start the Vite server: | ||
|
||
```shell | ||
npm i && npm run start | ||
``` | ||
|
||
The browser window will automatically open. Let's call this `Browser A`. | ||
Using the copied multiaddress from the Go or NodeJS 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 multiaddress located after the `Listening on` message. | ||
ckousik marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Now open a second browser with the url `http://localhost:5173/`. 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. | ||
`Browser B` is now connected to `Browser A`. | ||
Copy the multiaddress located after the `Listening on` message. | ||
|
||
Using the copied multiaddress from `Listening on` section in `Browser B`, paste it into the `Remote MultiAddress` input in `Browser A` and click the `Connect` button. | ||
`Browser A` is now connected to `Browser B`. | ||
|
||
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-webrtc-direct/p2p/12D3KooW9wFiWFELqGJTbzEwtByXsPiHJdHB8n7Kin71VMYyERmC | ||
Dialing '/ip4/127.0.0.1/tcp/57708/ws/p2p/12D3KooWRqAUEzPwKMoGstpfJVqr3aoinwKVPu4DLo9nQncbnuLk/p2p-circuit/p2p/12D3KooWBZyVLJfQkofqLK4op9TPkHuUumCZt1ybQrPvNm7TVQV9/p2p-webrtc-direct/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-webrtc-direct/p2p/12D3KooW9wFiWFELqGJTbzEwtByXsPiHJdHB8n7Kin71VMYyERmC' | ||
Listening on /ip4/127.0.0.1/tcp/57708/ws/p2p/12D3KooWRqAUEzPwKMoGstpfJVqr3aoinwKVPu4DLo9nQncbnuLk/p2p-circuit/p2p/12D3KooWBZyVLJfQkofqLK4op9TPkHuUumCZt1ybQrPvNm7TVQV9/p2p-webrtc-direct/p2p/12D3KooWBZyVLJfQkofqLK4op9TPkHuUumCZt1ybQrPvNm7TVQV9 | ||
Received message 'helloa' | ||
Sending message 'hellob' | ||
Received message 'hellob' | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<!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="connected_peer"></div> | ||
<div id="output"></div> | ||
</div> | ||
<script type="module" src="./index.js"></script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { multiaddr, protocols } from "@multiformats/multiaddr" | ||
import { pipe } from "it-pipe" | ||
import { fromString, toString } from "uint8arrays" | ||
import { webRTC } from "js-libp2p-webrtc" | ||
import { webSockets } from "@libp2p/websockets" | ||
import * as filters from "@libp2p/websockets/filters" | ||
import { pushable } from "it-pushable" | ||
import { mplex } from "@libp2p/mplex" | ||
import { createLibp2p } from "libp2p" | ||
import { circuitRelayTransport } from 'libp2p/circuit-relay' | ||
import { noise } from "@chainsafe/libp2p-noise" | ||
|
||
let webrtcDirectAddress | ||
|
||
const CIRCUIT_RELAY_CODE = 290 | ||
ckousik marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const WEBRTC_CODE = 281 | ||
|
||
const output = document.getElementById("output") | ||
const sendSection = document.getElementById("send-section") | ||
const peer = document.getElementById("peer") | ||
const appendOutput = (line) => { | ||
const div = document.createElement("div") | ||
div.appendChild(document.createTextNode(line)) | ||
output.append(div) | ||
} | ||
const clean = (line) => line.replaceAll("\n", "") | ||
const sender = pushable() | ||
|
||
const node = await createLibp2p({ | ||
transports: [ | ||
webSockets({ | ||
filter: filters.all, | ||
}), | ||
webRTC({}), | ||
circuitRelayTransport({ | ||
discoverRelays: 1, | ||
}), | ||
], | ||
connectionEncryption: [noise()], | ||
streamMuxers: [mplex()], | ||
}) | ||
|
||
await node.start() | ||
|
||
// handle the echo protocol | ||
await node.handle("/echo/1.0.0", ({ stream }) => { | ||
console.log("incoming stream") | ||
pipe( | ||
stream, | ||
async function* (source) { | ||
for await (const buf of source) { | ||
const incoming = toString(buf.subarray()) | ||
appendOutput(`Received message '${clean(incoming)}'`) | ||
yield buf | ||
} | ||
}, | ||
stream | ||
) | ||
}) | ||
|
||
node.peerStore.addEventListener("change:multiaddrs", (event) => { | ||
const { peerId } = event.detail | ||
|
||
if (node.getMultiaddrs().length === 0 || !node.peerId.equals(peerId)) { | ||
return | ||
} | ||
|
||
node.getMultiaddrs().forEach((ma) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be done by the library? Do we expect every user to do this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The webrtc library does not add any new multiaddresses to the peerStore. Given the discussion regarding multiaddresses, I feel it should add a new mulitaddress. However, the peerstore does not seem to have a way to atomically remove an address for a peer. https://github.com/libp2p/js-libp2p-interfaces/blob/master/packages/interface-peer-store/src/index.ts#L126 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you need to remove an address? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
if (ma.protoCodes().includes(CIRCUIT_RELAY_CODE)) { | ||
if (ma.protos().pop()?.name === 'p2p') { | ||
ma = ma.decapsulateCode(protocols('p2p').code) | ||
} | ||
const newWebrtcDirectAddress = multiaddr(ma.toString() + '/webrtc/p2p/' + node.peerId) | ||
ckousik marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This makes your address something like:
when it should be
Notice that the former has the p2p component multiple times. |
||
const webrtcAddrString = newWebrtcDirectAddress.toString() | ||
|
||
// only update if the address is new | ||
if (newWebrtcDirectAddress?.toString() !== webrtcDirectAddress?.toString()) { | ||
appendOutput(`Listening on '${webrtcAddrString}'`) | ||
sendSection.style.display = "block" | ||
webrtcDirectAddress = newWebrtcDirectAddress | ||
connected_peer.innerText = webrtcDirectAddress | ||
} | ||
} | ||
}) | ||
}) | ||
|
||
const isWebrtc = (ma) => { | ||
return ma.protoCodes().includes(WEBRTC_CODE) | ||
} | ||
|
||
window.connect.onclick = async () => { | ||
const ma = multiaddr(window.peer.value) | ||
appendOutput(`Dialing '${ma}'`) | ||
const connection = await node.dial(ma) | ||
|
||
if (!isWebrtc(ma)) { | ||
return | ||
} | ||
|
||
const outgoing_stream = await connection.newStream(["/echo/1.0.0"]) | ||
|
||
pipe(sender, outgoing_stream, async (src) => { | ||
for await (const buf of src) { | ||
const response = toString(buf.subarray()) | ||
appendOutput(`Received message '${clean(response)}'`) | ||
} | ||
}) | ||
} | ||
|
||
window.send.onclick = async () => { | ||
const message = `${window.message.value}\n` | ||
appendOutput(`Sending message '${clean(message)}'`) | ||
sender.push(fromString(message)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
{ | ||
"name": "js-libp2p-webrtc-private-to-private", | ||
"version": "1.0.0", | ||
"description": "Connect a browser to another browser", | ||
"type": "module", | ||
"scripts": { | ||
"start": "vite", | ||
"build": "vite build", | ||
"relay": "node relay.js", | ||
"test:firefox": "npm run build && playwright test --browser=firefox tests", | ||
"test:chrome": "npm run build && playwright test tests", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should add webkit, but this will fail until There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this a blocker @MarcoPolo or a new issue to work once libp2p/js-libp2p#1627 is completed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a blocker |
||
"test": "npm run test:firefox && npm run test:chrome" | ||
}, | ||
"dependencies": { | ||
"@chainsafe/libp2p-noise": "^11.0.0", | ||
"@libp2p/websockets": "^5.0.3", | ||
"@libp2p/mplex": "^7.0.0", | ||
"@multiformats/multiaddr": "^12.0.0", | ||
"it-pushable": "^3.1.0", | ||
"js-libp2p-webrtc": "file:../../", | ||
"libp2p": "^0.43.0", | ||
"vite": "^4.2.1" | ||
}, | ||
"devDependencies": { | ||
"@playwright/test": "^1.30.0", | ||
"test-util-ipfs-example": "^1.0.2" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { mplex } from "@libp2p/mplex" | ||
import { createLibp2p } from "libp2p" | ||
import { noise } from "@chainsafe/libp2p-noise" | ||
import { circuitRelayServer } from 'libp2p/circuit-relay' | ||
import { webSockets } from '@libp2p/websockets' | ||
import * as filters from '@libp2p/websockets/filters' | ||
|
||
const server = await createLibp2p({ | ||
addresses: { | ||
listen: ['/ip4/127.0.0.1/tcp/0/ws'] | ||
}, | ||
transports: [ | ||
webSockets({ | ||
filter: filters.all | ||
}), | ||
], | ||
connectionEncryption: [noise()], | ||
streamMuxers: [mplex()], | ||
relay: circuitRelayServer({}), | ||
}) | ||
|
||
console.log("p2p addr: ", server.getMultiaddrs().map((ma) => ma.toString())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should rename this to private-to-private or private browser-to-private browser to be better in line with current rename. Let's discuss in standup tomorrow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could see both sides of this. If we're looking for this example's name to have parity with the spec, then renaming makes sense. On the other hand, the example is a browser connecting to a browser, so the naming is aligned with the example. I don't have a strong opinion either way.