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

Feature/refactor and add tests and coverage #60

Merged
merged 19 commits into from
Dec 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
51 changes: 3 additions & 48 deletions .github/workflows/js-test-and-release.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
# File managed by web3-bot. DO NOT EDIT.
# See https://github.com/protocol/.github/ for details.

name: test & maybe release
on:
push:
branches:
- master # with #262 - ${{{ github.default_branch }}}
- main # with #262 - ${{{ github.default_branch }}}
pull_request:
branches:
- master # with #262 - ${{{ github.default_branch }}}
- main # with #262 - ${{{ github.default_branch }}}
- develop

jobs:
Expand Down Expand Up @@ -38,20 +35,6 @@ jobs:
with:
flags: chrome

test-chrome-webworker:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v2
with:
node-version: lts/*
- uses: ipfs/aegir/actions/cache-node-modules@master
- run: npm run --if-present test:chrome-webworker
- uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0
with:
flags: chrome-webworker

test-firefox:
needs: check
runs-on: ubuntu-latest
Expand All @@ -66,36 +49,8 @@ jobs:
with:
flags: firefox

test-firefox-webworker:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v2
with:
node-version: lts/*
- uses: ipfs/aegir/actions/cache-node-modules@master
- run: npm run --if-present test:firefox-webworker
- uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0
with:
flags: firefox-webworker

test-electron-main:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v2
with:
node-version: lts/*
- uses: ipfs/aegir/actions/cache-node-modules@master
- run: npx xvfb-maybe npm run --if-present test:electron-main
- uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0
with:
flags: electron-main

release:
needs: [test-chrome, test-chrome-webworker, test-firefox, test-firefox-webworker, test-electron-main]
needs: [test-chrome, test-firefox]
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/master' # with #262 - 'refs/heads/${{{ github.default_branch }}}'
steps:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public/css/main.css

# Coverage reports
coverage
.coverage
.nyc_output

# API keys and secrets
.env
Expand Down
123 changes: 89 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,23 @@
[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/)
[![IRC](https://img.shields.io/badge/freenode-%23libp2p-yellow.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p)
[![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/little-bear-labs//js-libp2p-webrtc.svg?style=flat-square)](https://codecov.io/gh/little-bear-labs//js-libp2p-webrtc)
[![CI](https://img.shields.io/github/workflow/status/libp2p/js-libp2p-interfaces/test%20&%20maybe%20release/master?style=flat-square)](https://github.com/little-bear-labs//js-libp2p-webrtc/actions/workflows/js-test-and-release.yml)
[![codecov](https://img.shields.io/codecov/c/github/little-bear-labs/js-libp2p-webrtc.svg?style=flat-square)](https://codecov.io/gh/little-bear-labs/js-libp2p-webrtc)
[![CI](https://img.shields.io/github/workflow/status/libp2p/js-libp2p-interfaces/test%20&%20maybe%20release/master?style=flat-square)](https://github.com/little-bear-labs/js-libp2p-webrtc/actions/workflows/js-test-and-release.yml)

> The browser implementation of the WebRTC module for libp2p.

## Table of contents <!-- omit in toc -->

- [Install](#install)
- [Usage](#usage)
- [API](#api)
- [Examples](#examples)
- [Interfaces](#interfaces)
- [Transport](#transport)
- [Connection](#connection)
- [Hacking](#hacking)
- [Contribute](#contribute)
- [Development](#development)
- [Build](#build)
- [Protocol Buffers](#protocol-buffers)
- [Test](#test)
- [Lint](#lint)
- [Clean](#clean)
- [Check Dependencies](#check-dependencies)
Expand All @@ -35,49 +36,81 @@ npm i @libp2p/webrtc
## Usage

```js
import { createLibp2pNode } from 'libp2p'
import { webRTC } from '@libp2p/webrtc'
import { noise } from '@chainsafe/libp2p-noise'
import { createLibp2p } from 'libp2p'
import { Noise } from '@chainsafe/libp2p-noise'
import { multiaddr } from '@multiformats/multiaddr'
import { pipe } from 'it-pipe'
import all from 'it-all'

const node = await createLibp2pNode({
transports: [
webRTC()
],
connectionEncryption: [
noise()
]
})

const addr = multiaddr('/ip4/0.0.0.0/udp/56093/webrtc/certhash/uEiByaEfNSLBexWBNFZy_QB1vAKEj7JAXDizRs4_SnTflsQ')
const { stream } = await node.dialProtocol(addr, '/my-protocol/1.0.0')
const values = await pipe(stream, all)
import first from "it-first";
import { pipe } from "it-pipe";
import { fromString, toString } from "uint8arrays";
import { webRTC } from 'js-libp2p-webrtc'

const node = await createLibp2p({
transports: [webRTC()],
connectionEncryption: [() => new Noise()],
});

await node.start()

const ma = multiaddr('/ip4/0.0.0.0/udp/56093/webrtc/certhash/uEiByaEfNSLBexWBNFZy_QB1vAKEj7JAXDizRs4_SnTflsQ')
const stream = await node.dialProtocol(ma, ['/my-protocol/1.0.0'])
const message = `Hello js-libp2p-webrtc\n`
const response = await pipe([fromString(message)], stream, async (source) => await first(source))
const responseDecoded = toString(response.slice(0, response.length))
```
## API

## Examples
Examples can be found in the [examples folder](examples/README.md).

## Interfaces

### Transport

[![](https://raw.githubusercontent.com/libp2p/js-libp2p-interfaces/master/packages/libp2p-interfaces/src/transport/img/badge.png)](https://github.com/libp2p/js-libp2p-interfaces/tree/master/packages/libp2p-interfaces/src/transport)
![https://github.com/libp2p/js-libp2p-interfaces/tree/master/packages/interface-transport](https://raw.githubusercontent.com/libp2p/js-libp2p-interfaces/master/packages/interface-transport/img/badge.png)

`libp2p-webrtc` accepts WebRTC encapsulated addresses: `/ip4/0.0.0.0/udp/56093/webrtc/certhash/uEiByaEfNSLBexWBNFZy_QB1vAKEj7JAXDizRs4_SnTflsQ`
Browsers can only `dial`, so `listen` is not supported.

### Connection
```js
interface Transport {
[Symbol.toStringTag]: string
Copy link

Choose a reason for hiding this comment

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

After reading up I found out what the implementation of this line would do, but I do not get why it is useful to declare here?

[symbol]: true
Copy link

Choose a reason for hiding this comment

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

What does this line do?

dial: (ma: Multiaddr, options: DialOptions) => Promise<Connection>
createListener: (options: CreateListenerOptions) => Listener
filter: MultiaddrFilter
}

class WebRTCTransport implements Transport {

async dial (ma: Multiaddr, options: WebRTCDialOptions): Promise<Connection> {
Copy link

Choose a reason for hiding this comment

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

Suggested change
async dial (ma: Multiaddr, options: WebRTCDialOptions): Promise<Connection> {
async dial (ma: Multiaddr, options?: WebRTCDialOptions): Promise<Connection> {

Given options implies it is optional, not better to make it clear that the object as a whole is optional? Or is there value in enforcing {} being passed in when no options are desired?

Or is it because there are properties in there required, but if so, it probably shouldn't be part of options?

const rawConn = await this._connect(ma, options)
log(`dialing address - ${ma.toString()}`)
Copy link

Choose a reason for hiding this comment

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

Suggested change
log(`dialing address - ${ma.toString()}`)
log(`dialing address - ${ma}`)

Fairly certain that toString() is implicitly called, no?
Tried my theory out and it does seem to be the case:

> class Foo { toString() { return "bar" } }
> console.log(`Hello, ${new Foo()}`)
Hello, bar

https://hacks.mozilla.org/2015/05/es6-in-depth-template-strings-2/ seems to agree with this as well.

Copy link

Choose a reason for hiding this comment

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

Also very minor, and perhaps I miss a convention here, but the log says dialing but at the point of the log you already dialed, no? So either we can simplify it to:

log(`dialing address - ${ma}`)
return this._connect(ma, options)

or else it perhaps is better to say dailed? Or am I missing something here?

return rawConn
}

createListener (options: CreateListenerOptions): Listener {
Copy link

Choose a reason for hiding this comment

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

if you turn your class into an abstract class (abstract class WebRTCTransport implements Transport) you should be able to just do:

abstract createListener (options: CreateListenerOptions): Listener

without having to specify your dummy body.

throw unimplemented('WebRTCTransport.createListener')
}
}
```

[![](https://raw.githubusercontent.com/libp2p/js-libp2p-interfaces/master/packages/libp2p-interfaces/src/connection/img/badge.png)](https://github.com/libp2p/js-libp2p-interfaces/tree/master/packages/libp2p-interfaces/src/connection)
### Connection

## Hacking
![https://github.com/libp2p/js-libp2p-interfaces/tree/master/packages/interface-connection](https://raw.githubusercontent.com/libp2p/js-libp2p-interfaces/master/packages/interface-connection/img/badge.png)
Copy link

Choose a reason for hiding this comment

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

Suggested change
![https://github.com/libp2p/js-libp2p-interfaces/tree/master/packages/interface-connection](https://raw.githubusercontent.com/libp2p/js-libp2p-interfaces/master/packages/interface-connection/img/badge.png)
[![Connection interface for libp2p](https://raw.githubusercontent.com/libp2p/js-libp2p-interfaces/master/packages/interface-connection/img/badge.png)](https://github.com/libp2p/js-libp2p-interfaces/tree/master/packages/interface-connection)

Not more accessible?


Besides the usual `npm install` to get dependencies, `npm run build` to invoke tsc, and `npm run test` to execute unit tests...
```js
interface MultiaddrConnection extends Duplex<Uint8Array> {
close: (err?: Error) => Promise<void>
remoteAddr: Multiaddr
Copy link

Choose a reason for hiding this comment

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

Does it has to be writable or can it be readonly? Same question for the next property.

timeline: MultiaddrConnectionTimeline
}

There is also `npm run autogen` which uses ProtoBuf's protoc to populate the generated code directory `proto_ts` based on `*.proto` files in src. Don't forget to run this step before `build` any time you make a change to any of the `*.proto` files.
class WebRTCMultiaddrConnection implements MultiaddrConnection { }
Copy link

Choose a reason for hiding this comment

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

Is there any value to this example as it:

  • neither implements the connection
  • nor does it get past a decent linter for several reasons

Just asking as people do tend to copy examples as is.

```

## Contribute

Contributions are welcome! The libp2p implementation in JavaScript is a work in progress. As such, there's a few things you can do right now to help out:

- [Check out the existing issues](//github.com/little-bear-labs//js-libp2p-webrtc/issues).
- [Check out the existing issues](//github.com/little-bear-labs/js-libp2p-webrtc/issues).
- **Perform code reviews**.
- **Add tests**. There can never be enough tests.
- Go through the modules and **check out existing issues**. This is especially useful for modules in active development. Some knowledge of IPFS/libp2p may be required, as well as the infrastructure behind it - for instance, you may need to read up on p2p and more complex operations like muxing to be able to help technically.
Expand All @@ -86,8 +119,6 @@ Please be aware that all interactions related to libp2p are subject to the IPFS

Small note: If editing the README, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification.

## Development

This module leans heavily on (Aegir)[https://github.com/ipfs/aegir] for most of the `package.json` scripts.

### Build
Expand All @@ -99,6 +130,30 @@ npm run build

The build will be located in the `/dist` folder.

### Protocol Buffers

There is also `npm run generate:proto` script that uses protoc to populate the generated code directory `proto_ts` based on `*.proto` files in src. Don't forget to run this step before `build` any time you make a change to any of the `*.proto` files.

### Test

To run all tests:

```shell
npm test
```

To run tests for Chome only:

```shell
npm run test:chrome
```

To run tests for Firefox only:

```shell
npm run test:firefox
```

### Lint
Aegir is also used to lint the code, which follows the [Standard](https://github.com/standard/standard) JS linter.
The VS Code plugin for this standard is located at https://marketplace.visualstudio.com/items?itemName=standard.vscode-standard.
Expand Down
4 changes: 4 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Examples

* [Browser to Server Echo](browser-to-server/README.md): connect to a go-libp2p-webrtc server with a browser

34 changes: 34 additions & 0 deletions examples/browser-to-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# js-libp2p-webrtc Browser to Server

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 Go Server

To run the Go LibP2P WebRTC server:

```shell
npm run go-libp2p-server
Copy link
Contributor

Choose a reason for hiding this comment

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

Am I to understand correctly that this command assumes you have go installed already? And at whatever version is necessary? Is it possible npm could make sure that's true for you?

```

Copy the multiaddress in the output.
Copy link

Choose a reason for hiding this comment

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

might be useful to show an example of such output and multiaddress


## 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.
Using the copied multiaddress from the Go server, paste it into the `Server MultiAddress` input and click the `Connect` button.
Once the peer is connected, click the message section will appear. Enter a message and click the `Send` button.
Copy link

Choose a reason for hiding this comment

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

Suggested change
Once the peer is connected, click the message section will appear. Enter a message and click the `Send` button.
Once the peer is connected, click the message section once it appeared. Then enter a message and click the `Send` button.

?


The output should look like:

```text
Dialing /ip4/10.0.1.5/udp/54375/webrtc/certhash/uEiADy8JubdWrAzseyzfXFyCpdRN02eWZg86tjCrTCA5dbQ/p2p/12D3KooWEG7N4bnZfFBNZE7WG6xm2P4Sr6sonMwyD4HCAqApEthb
Peer connected '/ip4/10.0.1.5/udp/54375/webrtc/certhash/uEiADy8JubdWrAzseyzfXFyCpdRN02eWZg86tjCrTCA5dbQ/p2p/12D3KooWEG7N4bnZfFBNZE7WG6xm2P4Sr6sonMwyD4HCAqApEthb'
Sending message 'hello'
Received message 'hello'
```
41 changes: 41 additions & 0 deletions examples/browser-to-server/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">Server 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>
39 changes: 39 additions & 0 deletions examples/browser-to-server/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { createLibp2p } from 'libp2p'
import { Noise } from '@chainsafe/libp2p-noise'
import { multiaddr } from '@multiformats/multiaddr'
import first from "it-first";
import { pipe } from "it-pipe";
import { fromString, toString } from "uint8arrays";
import { webRTC } from 'js-libp2p-webrtc'

let stream;
Copy link

Choose a reason for hiding this comment

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

Is this indention on purpose? o.O

Copy link

Choose a reason for hiding this comment

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

Or did you forget to define (async () => { on top?

Copy link

Choose a reason for hiding this comment

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

Even better, probably only want to do it once you know the DOM is initialised/ready?

const output = document.getElementById('output')
const sendSection = document.getElementById('send-section')
const appendOutput = (line) => output.innerText += `${line}\n`
const clean = (line) => line.replaceAll('\n', '')

const node = await createLibp2p({
transports: [webRTC()],
connectionEncryption: [() => new Noise()],
});

await node.start()
Copy link

Choose a reason for hiding this comment

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

Does this even run? Since when can you have await in a non-async body, which the global body is AFAIK?


node.connectionManager.addEventListener('peer:connect', (connection) => {
appendOutput(`Peer connected '${node.getConnections().map(c => c.remoteAddr.toString())}'`)
sendSection.style.display = 'block'
})

window.connect.onclick = async () => {
const ma = multiaddr(window.peer.value)
appendOutput(`Dialing ${ma}`)
stream = await node.dialProtocol(ma, ['/echo/1.0.0'])
}

window.send.onclick = async () => {
const message = `${window.message.value}\n`
appendOutput(`Sending message '${clean(message)}'`)
const response = await pipe([fromString(message)], stream, async (source) => await first(source))
const responseDecoded = toString(response.slice(0, response.length));
appendOutput(`Received message '${clean(responseDecoded)}'`)
}
Loading