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 16 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
63 changes: 63 additions & 0 deletions examples/browser-to-browser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# 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.

## Running the Relay Server

For browsers to communicate, we first need to run the Go LibP2P relay server:

```shell
npm run go-relay
```

Alternatively, a NodeJS relay server is available:

```shell
npm run node-relay
ckousik marked this conversation as resolved.
Show resolved Hide resolved
```

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'
```
41 changes: 41 additions & 0 deletions examples/browser-to-browser/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!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="output"></div>
</div>
<script type="module" src="./index.js"></script>
</body>
</html>
106 changes: 106 additions & 0 deletions examples/browser-to-browser/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { multiaddr } from "@multiformats/multiaddr"
import { pipe } from "it-pipe"
import { fromString, toString } from "uint8arrays"
import { webRTCDirect } 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 { noise } from "@chainsafe/libp2p-noise"

// singletons
let outgoing_stream
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this a singleton?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We are only sending on one stream in the example, so it is simpler to declare the variable early.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, I should have been more clear. This shouldn't be a singleton because
there's no benefit of it being so. I'm not sure you could use this in a correct
way as a singleton anyways, since once you pass it into pipe you don't own
this stream. Since this is an example, the risk that people copy this verbatim
is pretty high. so we should remove this. I'll push a change.

let webrtcDirectAddress

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 clean = (line) => line.replaceAll("\n", "")
const sender = pushable()

const node = await createLibp2p({
transports: [
webSockets({
filter: filters.all,
}),
webRTCDirect({}),
],
connectionEncryption: [noise()],
streamMuxers: [mplex()],
relay: {
enabled: true,
autoRelay: {
enabled: true,
},
},
})

await node.start()

// handle the echo protocol
await node.handle("/echo/1.0.0", ({ 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(290)) {
const newWebrtcDirectAddress = ma.encapsulate(
multiaddr(`/p2p-webrtc-direct/p2p/${node.peerId}`)
)

// only update if the address is new
if (newWebrtcDirectAddress?.toString() !== webrtcDirectAddress?.toString()) {
appendOutput(`Listening on ${newWebrtcDirectAddress}`)
sendSection.style.display = "block"
webrtcDirectAddress = newWebrtcDirectAddress
}
}
})
})

window.connect.onclick = async () => {
const ma = multiaddr(window.peer.value)
appendOutput(`Dialing '${ma}'`)
const connection = await node.dial(ma)

if (!ma.protoCodes().includes(276)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

276? Let's not forget to change this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

276 is the protoCode for circuit-relay. This is just used to differentiate between dialing a relay peer to start listening vs dialing a peer over relay to create an echo stream.

Copy link
Member

Choose a reason for hiding this comment

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

is there a library where we can ensure the protocode is more self documenting? (similar to codes in dag-json, dag-cbor, etc.. from ipld) we shouldn't be throwing magic numbers around

return
}

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))
}
26 changes: 26 additions & 0 deletions examples/browser-to-browser/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "js-libp2p-webrtc-browser-to-server",
"version": "1.0.0",
"description": "Connect a browser to a server",
ckousik marked this conversation as resolved.
Show resolved Hide resolved
"type": "module",
"scripts": {
"start": "vite",
"build": "vite build",
"go-relay": "cd ../go-libp2p-server && go run ./relay",
"node-relay": "node relay.js",
"test": "npm run build && playwright test tests"
},
"dependencies": {
"@chainsafe/libp2p-noise": "^11.0.0",
"@libp2p/websockets": "^5.0.3",
"@multiformats/multiaddr": "^11.0.5",
"it-pushable": "^3.1.0",
"js-libp2p-webrtc": "file:../../",
"libp2p": "^0.42.0",
ckousik marked this conversation as resolved.
Show resolved Hide resolved
"vite": "^3.1.0"
},
"devDependencies": {
"@playwright/test": "^1.30.0",
"test-util-ipfs-example": "^1.0.2"
}
}
38 changes: 38 additions & 0 deletions examples/browser-to-browser/relay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { createLibp2p } from 'libp2p'
import { webSockets } from '@libp2p/websockets'
import { noise } from '@chainsafe/libp2p-noise'
import { mplex } from '@libp2p/mplex'

async function main () {
const node = await createLibp2p({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0/ws']
// TODO check "What is next?" section
// announce: ['/dns4/auto-relay.libp2p.io/tcp/443/wss/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3']
ckousik marked this conversation as resolved.
Show resolved Hide resolved
},
transports: [
webSockets()
],
connectionEncryption: [
noise()
],
streamMuxers: [
mplex()
],
relay: {
enabled: true,
hop: {
enabled: true
},
advertise: {
enabled: true,
}
}
})

console.log(`Node started with id ${node.peerId.toString()}`)
console.log('Listening on:')
node.getMultiaddrs().forEach((ma) => console.log(ma.toString()))
}

main()
97 changes: 97 additions & 0 deletions examples/browser-to-browser/tests/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/* eslint-disable no-console */
import { test, expect } from '@playwright/test'
import { playwright } from 'test-util-ipfs-example'
import { spawn, exec } from 'child_process'
import { existsSync } from 'fs'

// Setup
const play = test.extend({
...playwright.servers()
})

async function spawnGoLibp2p() {
if (!existsSync('../../examples/go-libp2p-server/go-libp2p-server')) {
await new Promise((resolve, reject) => {
exec('go build',
{ cwd: '../../examples/go-libp2p-server' },
(error, stdout, stderr) => {
if (error) {
throw (`exec error: ${error}`)
}
resolve()
})
})
}

const server = spawn('./go-libp2p-server', [], { cwd: '../../examples/go-libp2p-server', killSignal: 'SIGINT' })
server.stderr.on('data', (data) => {
console.log(`stderr: ${data}`, typeof data)
})
const serverAddr = await (new Promise(resolve => {
server.stdout.on('data', (data) => {
console.log(`stdout: ${data}`, typeof data)
const addr = String(data).match(/p2p addr: ([^\s]*)/)
if (addr !== null && addr.length > 0) {
resolve(addr[1])
}
})
}))
return { server, serverAddr }
}

play.describe('bundle ipfs with parceljs:', () => {
// DOM
const connectBtn = '#connect'
const connectAddr = '#peer'
const messageInput = '#message'
const sendBtn = '#send'
const output = '#output'

let server
let serverAddr

// eslint-disable-next-line no-empty-pattern
play.beforeAll(async ({ }, testInfo) => {
testInfo.setTimeout(5 * 60_000)
const s = await spawnGoLibp2p()
server = s.server
serverAddr = s.serverAddr
console.log('Server addr:', serverAddr)
}, {})

play.afterAll(() => {
server.kill('SIGINT')
})

play.beforeEach(async ({ servers, page }) => {
const url = `http://localhost:${servers[0].port}/`
console.log(url)
await page.goto(url)
})

play('should connect to a go-libp2p node over webtransport', async ({ page }) => {
const message = 'hello'

// add the go libp2p multiaddress to the input field and submit
await page.fill(connectAddr, serverAddr)
await page.click(connectBtn)

// send the relay message to the go libp2p server
ckousik marked this conversation as resolved.
Show resolved Hide resolved
await page.fill(messageInput, message)
await page.click(sendBtn)

await page.waitForSelector('#output:has(div)')

// Expected output:
//
// Dialing '${serverAddr}'
// Peer connected '${serverAddr}'
// Sending message '${message}'
// Received message '${message}'
const connections = await page.textContent(output)
expect(connections).toContain(`Dialing '${serverAddr}'`)
expect(connections).toContain(`Peer connected '${serverAddr}'`)
expect(connections).toContain(`Sending message '${message}'`)
expect(connections).toContain(`Received message '${message}'`)
})
})
11 changes: 11 additions & 0 deletions examples/browser-to-browser/vite.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default {
build: {
target: 'es2022'
},
optimizeDeps: {
esbuildOptions: { target: 'es2022', supported: { bigint: true } }
},
server: {
open: true
}
}
Loading