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

Commit

Permalink
feat: Browser to Browser (#90)
Browse files Browse the repository at this point in the history
* browser-to-browser over relayv1

* debug

* fix race condition

* fixes

* browser to browser

* fix tests

* add tests for handlers

* fix dep-check

* Add browser to browser example with Node and Go relays

* fix browser-to-browser example

* Attempt to fix example

* race condition

* buffer incoming streams before upgrade

* update stream protocol name

* Complete the browser-to-browser example

* address review

* clarify circuit relay code usage

* fix lint

* add explicit eslint dependency

* fix dependencies

* Implement single browser for browser-to-browser in CI

* Complete playwright test for communicating browsers and add to CI

* wrap errors

* fix connection initiation

* unskip filter test

* fix Go version to 1.19 for compiling go-libp2p

* fix protocol codes and update multiaddr library

* skip localhost test on firefox

* nicer fix

* add tests example tests for firefox and chrome

* Start rename (bugs)

* use js relay in browser-to-browser tests

* hacky fixes to tests

* fix firefox issue

* more fixes

* fixes

* fix review comments

* Support dialing shorter circuit addrs

* Use shorter multiaddr in example

* add answerpromise

* Fix README for example

* Remove singleton

* Type the promise

* Fix comment

* Refactor: Consolidate event handlers in resolveOnConnected

* Nits

* Update go multiaddr dep

* Fix browser-to-server example

* Nit

* Workaround to not listen on p2p-circuit

* workaround: Fix multiaddr splitting

* Small rename cleanup

---------

Co-authored-by: David DiMaria <info@collectivesessions.com>
Co-authored-by: Marco Munizaga <git@marcopolo.io>
  • Loading branch information
3 people committed Apr 7, 2023
1 parent 28b2aad commit add5c46
Show file tree
Hide file tree
Showing 28 changed files with 1,319 additions and 517 deletions.
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

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

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.

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
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) => {
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)

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",
"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

0 comments on commit add5c46

Please sign in to comment.