webrtc RTCPeerConnection in a web-component
@sctlib/rtcPrototype web-components to establish
webrtc peer connections,
without the need of a predefined signaling server.
> There are different signaling methods, some like matrix will call a matrix
> server.
- chat #sctlib-rtc:matrix.org
- npm package
- gitlab code
- example "end user" usage videos.
To connect with peers using WebRTC, visit the
website. From the interface, select a "signaling
method" and follow the instructions, to connect with an other peer (a friend, or
yourself in the same browser tab, an other tab, an other brwoser, an other
device, from an other network etc. )
This project is available as a web page, and a javascript/html/css/web-component
npm package.
1. npm i @sctlib/rtc
1. check this project index.html file to see how to init the components, it is
this demo page.
The web-components definitions can be imported from a CDN, and inserted in the HTML DOM with:
``html`
To establish a webrtc connection between two peers (A, instanciatingB
the connection, and with whom we are trying to connect), data has
two be exchanged between them, this is the signaling part of the
connection.
1. peer A creates a RTCPeerConnection, and an offerpeer A
1. signals the offer to peer Bpeer B
1. receives/read the offer, creates its own RTCPeerconnection, and creates and answerpeer B
1. signals the answer to peer Apeer A
1. receive/reads the answer, adds it to its RTCPeerconnectionpeer A
1. a webrtc connection between and peer B is opened
To signal the offer and answer between peer A and B, we could use a server
(which would transmit the data for us, the "signaling" part of a WebRTC
connection, before the RTCConnection is established), but we want to be able to
to that without relying on a specific server somewhere (which might be offline,
unavailable, or we just want to be independant, decentralized, autonomous, p2p...).
For this reason we want to "manually signal" the data (see peer data
section) ourselves in different ways. These signaling forms can be
composed to establish a connection, so each independant part could be
transmitted with different method (as long as peer A and B get what
they need, in the correct order).
- peer A copy/paste the offer data, singaling it to peer B over a _third party signaling server_peer B
- copy/paste the offer from peer A, into the app to generate the peer anwserpeer B
- copy/paste the answer and signals it to peer A over a _third party signaling server_peer A
- copy/paste the answer into the app, to finish the connection
Third party manual signaling servers could be:
- matrix, signal, whatsapp, telegram, instagram, steam, in app chat etc.
- bookmark synchronisation service, to share the data as URL between devices
- (push) notifications, such as ntfy; (problem is, there are
rate limit for non paying user, so rolled out currently).
For devices which support displaying QR codes, and/or reading them, it
is possible to generate a QR-code representation of all peer data that
needs to be signaled.
A device can create peer offerbundle, and display it as a
qr-code. It can also display it "unbundled", with multiple qr-codes
representing the same data.
A device can also read the qr-code of peer data (bundled or not), in
one qr-code, or multiple qr-codes displayed sequencecially. This
feature is possible thanks to the barcode-detector-polyfill andzbar-wasm qr-code scanning
modules
(not bundled into the app, but served from a CDN because of their
licences).
- when receiving an offer, we can signal the peer contained in the?data
query parameter, to our local peer, so it creates an answeranswer
(to be signaled back to the instanciating peer)
- when receiving an , we can also signal the incoming peer?data
data contained in the query param, to our local peer (so it
can finish opening the connection)
The peer(ing) data, is the data sent between peers to instanciate a
webrtc peer connection. It is composed of:
- a webrtc peer offer or answer, (a javascript object) description
of the peer with RTC capabilities
- a list of ICECandidates, the possibe routes to our peer on the network
For signaling purpose, this data is transformed as a string of text, so it can:
- fit into the URL/URI scheme (here as a ?data= query parameter),
and not be too long (gitlab pages only support ±1048 chars urls; we moved the app to cloudflare pages).
- fit into QR-codes and their maximum size
- be accessible, received and read by various (people and) devices, with multiple
form factors and capabilities
> researching how to best encode, compress, transmit, sign,
> the data in these "manual ways" so it is relyiable.
- right it uses lz-strings to compress and encode the data for the URL.atob
- used to be using base64 ( and btoa, with JSON.parse() and JSON.stringify())protobuff
- could research , brocli, or UDP/TCP (or other data
transmition protocol), to experiment with multi QR-code send/receive
(device facing device, both doing both actions).
The data can also be signaled:
- "bundled", an object with offer/answer + candidates, (answerbundle and offerbundle)offer
- "unbundled", several objects, /answer, candidates, type
To use the code locally, or in other applications.
Run dev server with:
`bash
npm install
npm run dev
`
There is currently only one "public" web component though it is composed of a
few others, that are undocumented, but should all work independantly.
A few HTML attributes allow to customize the experience of the rtc user.
Some are set in the DOM by the user, some other are set by the app, as reflection of its current state, and can be used to style the interface (but not for changing the state).
When inserted in the DOM, will create an interface to allow two peers to connect, with various signaling methods.
#### Attributes
- user-name = String, only a display for the local user peer'ssignaling-methods
- = JSON.stringify(Array[signaling-methods]), a list of allowed signaling methods IDs.matrix-peers
- = JSON.stringify(Array[matrix_user_id]), is a list of Matrix.org user IDs that are allowed for incoming RTC offers (and preffiled for outgoing RTC offer/answers)search-params
- = JSON.stringify(Array[ than can be preffiled from the current web page's URL search params (hash params too?)
Examples:
`html
`
#### Events
Currently only two events are sent outside.
`js
const $user = document.querySelector('rtc-user')
$user.addEventListener('dataChannel', (event)=> {
const {detail} = event
console.log('Peer connected, received data channel', event)
console.log('Peer connection data channel', detail)
})
$user.addEventListener('channelMessage', (event)=> {
const {detail} = event
console.log(Peer received message from data channel, event)
console.log(Peer message , detail)
})
`
#### Methods
When a data channel is open, there are several methods that can be used on the rtc-user:
- send(data) accepts data to be sen through the data channel. As a convenience, if it typeof data === "object", JSON.stringify(data) will be applied, so the data can be send as a Javascript string.
#### Slots
The rtc-user has two slots, that can be used to render HTML elements as it's children; logs and send:
`html`
The slots, do not handle automatically events (as we might expect; for this we'd have to clearely define the events/methods api for inserted elements into the slots; so the rtc-user can communicate with them directly).
To handle the slots, such as what's done with their default slot value (a textarea to send data, and a list of the messages as logs):
`js
// the HTML elements we need for displaying rtc events
const $user = document.querySelector("rtc-user");
const $userLog = $user.querySelector('[slot="logs"]');
const $userSend = $user.querySelector('[slot="send"]');
// output the opened data channel (in/out) to the logs
$user.addEventListener("dataChannel", (event) => {
if ($userLog) {
const $log = document.createElement("p");
$log.innerText = event.detail.type;
$userLog.append($log);
}
});
// listen to rtc data channel messages in; append all as logs
$user.addEventListener("channelMessage", (event) => {
console.log("outside user receive channel message", event);
if ($userLog) {
const $log = document.createElement("p");
$log.innerText = event.detail;
$userLog.append($log);
}
});
// listen to the form "submit" event, send content through rtc data channel out
if ($userSend) {
$userSend.addEventListener("submit", (event) => {
event.preventDefault();
const formData = new FormData(event.target);
const formDataObject = Object.fromEntries(formData);
$user.send({
detail: formDataObject,
});
});
}
`
This project is an experiment in establishing a web RTCPeerConnection, between
two peers in their browsers, without the need of a predefined server to
establing signaling
- Peer sends a connection offer to Peer BPeer B
- receives and uses the offer sent by Peer A, to create an answer, and sends it to Peer APeer A` receives the answer, and associate it to its peer.
-
- The RTCPeerConnection succeeds (or fails), the two peers are now
connected for real time communication, without intermediary
server.
To get started, send an offer to a friend, and wait for them to send
you an answer back.
As a first test, try the "copy/paste" signaling method. It is also possible to
try signaling with QR-code(s). Open the developer console for debugging
information.
We're using a bunch of rtc components to connect peers with webrtc, without the
need of servers.
There are different scenarios we're trying to cover.
1. 1 peer alone -> no data channel
1. 2 peers, on the same browser, in 1 page
1. 2 peers, on the same browser, in 1 page, in 2 iframes
1. 2 peers, on the same browser, in 2 pages (tabs which are publich; not private browsing)
1. 2 peers, on the same browser, 1 public page, and 1 in private browsing page
1. 2 peers, on different browsers (private or public pages), on the same computer
1. 2 peers, on different browsers (private or public pages), on different computers, on the same network
1. 2 peers, on different browsers (private or public tabs), on different computers, on different networks (behind NAT/ROUTER)
1. 2 peers, different computer/browser/network, through private VPN
1. X peers, <- start again from 1.
It would be nice to have most of them working without the need of any server (signaling/TURN; STUN, since free are okay)?
Status:
- done: 1, 2, 3, 4
- doing: 5, 6, 7, (8 & 9)
- after: 10
(a)GPLv3 https://www.gnu.org/licenses/gpl-3.0.en.html