Skip to content
This repository has been archived by the owner on Jun 19, 2023. It is now read-only.

feat: Browser to Browser #90

Merged
merged 56 commits into from
Apr 7, 2023
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
07b5011
browser-to-browser over relayv1
ckousik Jan 3, 2023
cf7c2bd
debug
ckousik Jan 3, 2023
f2fc6e3
fix race condition
ckousik Jan 5, 2023
eefde36
Merge branch 'main' into feat/browser-to-browser
ckousik Feb 6, 2023
3abb661
fixes
ckousik Feb 6, 2023
e50dd76
browser to browser
ckousik Feb 8, 2023
84c5b3c
fix tests
ckousik Feb 8, 2023
5e3d29e
add tests for handlers
ckousik Feb 8, 2023
43246b4
fix dep-check
ckousik Feb 8, 2023
6472150
Add browser to browser example with Node and Go relays
ddimaria Feb 13, 2023
83cd254
fix browser-to-browser example
ckousik Feb 14, 2023
f786bdb
Attempt to fix example
ddimaria Feb 14, 2023
5a08ed7
race condition
ckousik Feb 14, 2023
0ba969c
buffer incoming streams before upgrade
ckousik Feb 15, 2023
e1bf7de
update stream protocol name
ckousik Feb 15, 2023
eafad87
Complete the browser-to-browser example
ddimaria Feb 17, 2023
40d72fb
address review
ckousik Feb 21, 2023
c24fb7e
clarify circuit relay code usage
ckousik Feb 21, 2023
daed656
Merge branch 'main' into feat/browser-to-browser
ckousik Feb 21, 2023
df5ce02
fix lint
ckousik Feb 21, 2023
20e7c28
add explicit eslint dependency
ckousik Feb 21, 2023
8ea980b
fix dependencies
ckousik Feb 21, 2023
69c4208
Merge branch 'main' into feat/browser-to-browser
ckousik Feb 22, 2023
d622b27
Implement single browser for browser-to-browser in CI
ddimaria Feb 22, 2023
ddad046
Complete playwright test for communicating browsers and add to CI
ddimaria Feb 22, 2023
602adc9
wrap errors
ckousik Feb 24, 2023
c8c2888
fix connection initiation
ckousik Mar 6, 2023
41c8e2d
unskip filter test
ckousik Mar 6, 2023
a031dca
Merge branch 'main' into feat/browser-to-browser
ckousik Mar 6, 2023
40fd90c
fix Go version to 1.19 for compiling go-libp2p
ckousik Mar 7, 2023
d4a6858
fix protocol codes and update multiaddr library
ckousik Mar 7, 2023
0b6a80f
skip localhost test on firefox
ckousik Mar 7, 2023
6e8b3f6
nicer fix
ckousik Mar 7, 2023
32aa877
add tests example tests for firefox and chrome
ckousik Mar 9, 2023
d167645
Start rename (bugs)
ckousik Mar 21, 2023
55c4f7b
use js relay in browser-to-browser tests
ckousik Mar 23, 2023
e7a2623
hacky fixes to tests
ckousik Mar 24, 2023
45f47f2
fix firefox issue
ckousik Mar 24, 2023
05c783d
more fixes
ckousik Mar 24, 2023
eb64c85
fixes
ckousik Mar 24, 2023
9d831a8
fix review comments
ckousik Mar 28, 2023
08575d4
Support dialing shorter circuit addrs
MarcoPolo Mar 30, 2023
f2df558
Use shorter multiaddr in example
MarcoPolo Mar 30, 2023
a8efbfe
add answerpromise
ckousik Mar 31, 2023
0a9f5d3
Fix README for example
ckousik Apr 3, 2023
a86eb4b
Remove singleton
MarcoPolo Apr 3, 2023
497e7e2
Type the promise
MarcoPolo Apr 3, 2023
57ad032
Fix comment
MarcoPolo Apr 3, 2023
c30664b
Refactor: Consolidate event handlers in resolveOnConnected
MarcoPolo Apr 3, 2023
257101e
Nits
MarcoPolo Apr 3, 2023
489876b
Update go multiaddr dep
MarcoPolo Apr 4, 2023
256fc95
Fix browser-to-server example
MarcoPolo Apr 4, 2023
7b9e98f
Nit
MarcoPolo Apr 4, 2023
57779d4
Workaround to not listen on p2p-circuit
MarcoPolo Apr 6, 2023
2184275
workaround: Fix multiaddr splitting
MarcoPolo Apr 6, 2023
2f77031
Small rename cleanup
MarcoPolo Apr 7, 2023
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
5 changes: 3 additions & 2 deletions .github/workflows/examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
matrix:
project:
- browser-to-server
- browser-to-browser
defaults:
run:
working-directory: examples/${{ matrix.project }}
Expand All @@ -30,7 +31,7 @@ jobs:
node-version: lts/*
- uses: actions/setup-go@v3
with:
go-version: '>=1.19.0'
go-version: '1.19'
- name: Install dependencies
run: npm install
working-directory: .
Expand All @@ -44,4 +45,4 @@ jobs:
- name: Run tests
run: npm run test
env:
CI: true
CI: true
61 changes: 61 additions & 0 deletions examples/browser-to-browser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# js-libp2p-webrtc Browser to Browser
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# js-libp2p-webrtc Browser to Browser
# js-libp2p-webrtc Browser to Browser

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

Copy link
Collaborator

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.


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'
```
42 changes: 42 additions & 0 deletions examples/browser-to-browser/index.html
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>
115 changes: 115 additions & 0 deletions examples/browser-to-browser/index.js
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) => {
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Collaborator Author

@ckousik ckousik Mar 24, 2023

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to remove an address?

Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Contributor

@MarcoPolo MarcoPolo Mar 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes your address something like:

/ip4/127.0.0.1/tcp/50000/ws/p2p/12D3KooWQyApav13VMvRuFQctE7nyxiv2XgQ1mGenfEhLqJyyjxJ/p2p-circuit/p2p/12D3KooWMSgjouGLHPTQzwC2CWDPo4G71swUf88gHpdZvNZpGNEM/webrtc/p2p/12D3KooWMSgjouGLHPTQzwC2CWDPo4G71swUf88gHpdZvNZpGNEM

when it should be

/ip4/127.0.0.1/tcp/50000/ws/p2p/12D3KooWQyApav13VMvRuFQctE7nyxiv2XgQ1mGenfEhLqJyyjxJ/p2p-circuit/webrtc/p2p/12D3KooWMSgjouGLHPTQzwC2CWDPo4G71swUf88gHpdZvNZpGNEM

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))
}
28 changes: 28 additions & 0 deletions examples/browser-to-browser/package.json
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",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add webkit, but this will fail until
libp2p/js-libp2p#1627 is fixed.

Copy link
Collaborator

Choose a reason for hiding this comment

The 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?

Copy link
Contributor

Choose a reason for hiding this comment

The 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"
}
}
22 changes: 22 additions & 0 deletions examples/browser-to-browser/relay.js
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()))
Loading