@libp2p/webrtc

A libp2p transport using WebRTC connections
About
A libp2p transport based on WebRTC data channels.
WebRTC is a specification that allows real-time communication between nodes - it's commonly used in browser video conferencing applications but it also provides a reliable data transport mechanism called data channels which libp2p uses to facilitate protocol streams between peers.
There are two transports exposed by this module, webRTC and webRTCDirect.
WebRTC vs WebRTC Direct
The connection establishment phase of WebRTC involves a handshake using SDP during which two peers will exchange information such as open ports, network addresses and required capabilities.
A third party is usually necessary to carry out this handshake, forwarding messages between the two peers until they can make a direct connection between themselves.
The WebRTC transport uses libp2p Circuit Relays to forward SDP messages. Once a direct connection is formed the relay plays no further part in the exchange.
WebRTC Direct uses a technique known as SDP munging to skip the handshake step, instead encoding enough information in the connection request that the responder can derive what would have been in the handshake messages and so requires no third parties to establish a connection.
A WebRTC Direct multiaddr also includes a certhash of the target peer - this is used to allow opening a connection to the remote, which would otherwise be denied due to use of a self-signed certificate.
In both cases, once the connection is established a Noise handshake is carried out to ensure that the remote peer has the private key that corresponds to the public key that makes up their PeerId, giving you both encryption and authentication.
Example - WebRTC
WebRTC requires use of a relay to connect two nodes. The listener first discovers a relay server and makes a reservation, then the dialer can connect via the relayed address.
import { noise } from '@chainsafe/libp2p-noise'
import { yamux } from '@chainsafe/libp2p-yamux'
import { echo } from '@libp2p/echo'
import { circuitRelayTransport, circuitRelayServer } from '@libp2p/circuit-relay-v2'
import { identify } from '@libp2p/identify'
import { webRTC } from '@libp2p/webrtc'
import { webSockets } from '@libp2p/websockets'
import { WebRTC } from '@multiformats/multiaddr-matcher'
import delay from 'delay'
import { pipe } from 'it-pipe'
import { createLibp2p } from 'libp2p'
import type { Multiaddr } from '@multiformats/multiaddr'
const relay = await createLibp2p({
addresses: {
listen: ['/ip4/127.0.0.1/tcp/0/ws']
},
transports: [
webSockets()
],
connectionEncrypters: [noise()],
streamMuxers: [yamux()],
connectionGater: {
denyDialMultiaddr: () => false
},
services: {
identify: identify(),
relay: circuitRelayServer()
}
})
const listener = await createLibp2p({
addresses: {
listen: [
'/p2p-circuit',
'/webrtc'
]
},
transports: [
webSockets(),
webRTC(),
circuitRelayTransport()
],
connectionEncrypters: [noise()],
streamMuxers: [yamux()],
connectionGater: {
denyDialMultiaddr: () => false
},
services: {
identify: identify(),
echo: echo()
}
})
await listener.dial(relay.getMultiaddrs(), {
signal: AbortSignal.timeout(5000)
})
let webRTCMultiaddr: Multiaddr | undefined
while (true) {
webRTCMultiaddr = listener.getMultiaddrs().find(ma => WebRTC.matches(ma))
if (webRTCMultiaddr != null) {
break
}
await delay(1000)
}
const dialer = await createLibp2p({
transports: [
webSockets(),
webRTC(),
circuitRelayTransport()
],
connectionEncrypters: [noise()],
streamMuxers: [yamux()],
connectionGater: {
denyDialMultiaddr: () => false
},
services: {
identify: identify(),
echo: echo()
}
})
const stream = await dialer.dialProtocol(webRTCMultiaddr, dialer.services.echo.protocol, {
signal: AbortSignal.timeout(5000)
})
await relay.stop()
await pipe(
[new TextEncoder().encode('hello world')],
stream,
async source => {
for await (const buf of source) {
console.info(new TextDecoder().decode(buf.subarray()))
}
}
)
Example - WebRTC Direct
WebRTC Direct allows a client to establish a WebRTC connection to a server
without using a relay to first exchange SDP messages.
Instead the server listens on a public UDP port and embeds its certificate
hash in the published multiaddr. It derives the client's SDP offer based on
the incoming IP/port of STUN messages sent to this public port.
The client derives the server's SDP answer based on the information in the
multiaddr so no SDP handshake via a third party is required.
Full details of the connection protocol can be found in the WebRTC Direct spec.
Browsers cannot listen on WebRTC Direct addresses since they cannot open
ports, but they can dial all spec-compliant servers.
Node.js/go and rust-libp2p can listen on and dial WebRTC Direct addresses.
import { createLibp2p } from 'libp2p'
import { multiaddr } from '@multiformats/multiaddr'
import { pipe } from 'it-pipe'
import { fromString, toString } from 'uint8arrays'
import { webRTCDirect } from '@libp2p/webrtc'
const listener = await createLibp2p({
addresses: {
listen: [
'/ip4/0.0.0.0/udp/0/webrtc-direct'
]
},
transports: [
webRTCDirect()
]
})
await listener.start()
const dialer = await createLibp2p({
transports: [
webRTCDirect()
]
})
await dialer.start()
const stream = await dialer.dialProtocol(listener.getMultiaddrs(), '/my-protocol/1.0.0', {
signal: AbortSignal.timeout(10_000)
})
await pipe(
[fromString(`Hello js-libp2p-webrtc\n`)],
stream,
async function (source) {
for await (const buf of source) {
console.info(toString(buf.subarray()))
}
}
)
Install
$ npm i @libp2p/webrtc
Browser <script>
tag
Loading this module through a script tag will make its exports available as Libp2pWebrtc
in the global namespace.
<script src="https://unpkg.com/@libp2p/webrtc/dist/index.min.js"></script>
API Docs
License
Licensed under either of
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.