npm package: @jacopovalanzano/ts-p2p
Description:  TypeScript WebRTC peer-to-peer data transfer
Author: Jacopo Valanzano
Latest version: 1.0.0

# WebRTC: Easy peer-to-peer communication

WebRTC is a technology that enables real-time peer-to-peer communication directly between remote 
hosts—often referred to as 'peers'—in this case, web browsers, like Google Chrome, Safari, and Firefox.

_STUN_, a protocol that is part of WebRTC, allows audio, video, and data to be exchanged without the need for a
middleman (a relay server). That means that the connection is direct between the two peers.
It's an efficient way to build applications for video conferencing, file sharing, and live data communication.

# Example usage:

To establish a peer-to-peer WebRTC connection, one peer serves as the initiator and the other as the receiver.

The initiator establishes the conditions for the connection by creating and sending a session description protocol
(SDP) offer to the receiver. The receiver accepts the offer and creates an SDP answer, which is then sent back
to the initiator. The initiator uses the SDP answer from the receiver to finalize the connection and begin
sending/receiving data.

Primarily, RTC uses two mechanisms for data transmission, *Media Streams* (Tracks) and *Data Channels*.

## Create a data channel

Data channels in WebRTC are used to exchange arbitrary data directly between peers.  
You can use them to transfer files, chat messages, or any other kind of data.

Here's an example of how to create a data channel and send a message.

### Negotiation (or handshake): establishing a data channel

#### Initiator

The initiator creates an offer:

    const initiator = new Initiator(); 
    const offer = await initiator.createDataChannelOffer( "text-channel-1" );  

#### Receiver

The receiver parses the offer given by the initiator and creates an answer:

    const receiver = new Receiver();
    const answer = await receiver.processOffer( offer ); 

#### Initiator

The initiator parses the answer given by the receiver, and the peers can now establish a connection:

    ...
    initiator.processAnswer( answer );

### Connecting, sending and receiving data

Once the `initiator` has finished processing the answer, the two peers can connect.
The initiator (also known as the offerer) creates and opens the data channel;
the receiver (also known as the answerer) waits for the data channel to be opened.
As soon as the data channel is open, the peers can exchange data.

To log the data we receive, we can conveniently use an event listener by listening for the `message` event
with `addEventListener`, rather than assigning a handler to the `onmessage` property.
This allows us to attach multiple listeners without directly interfering with the WebRTC API:

    initiator.getPeerConnection().getDataChannel( "text-channel-1" ).addEventListener("message", (event: MessageEvent) => {  
      console.log( "Message from text-channel-1", event.data );  
    });

The `receiver`, instead, could use the `datachannel` event.
In the example below, the receiver listens for a data channel opening by using an event listener;
soon after, we add another event listener that logs the received messages to the console.
This is because the receiver has no way to identify a channel by label before the channel is open:

    receiver.getPeerConnection().addEventListener(  
      "datachannel",  
      (event) => {
	      const label = event.channel.label;
	      event.channel.addEventListener("message", (event: MessageEvent) => {  
		      console.log( "Message from " + label, event.data );  
	      });  
      }  
    )


RTC offers five different event handlers for the data channel, `onerror`, `onopen`, `onclose`, `onmessage` and `onbufferedamountlow`.

One thing to remember is the data channel _label_.  
You can create multiple channels to exchange data; channels can be created between the same two remote peers, or between different remote peers. To distinguish between data channels, RTC uses labels. Each channel also has a numeric _id_ assigned to it, but IDs are often auto-generated by the WebRTC engine; thus, to distinguish between channels, labels are the way to go. In the example above, `text-channel-1` is the data channel label.

## Create a media stream

Media streams are a much more efficient way to stream and transmit real-time data, such as video and audio.

In this example, we will establish a two-way stream of data, displaying webcam feeds from two remote peers:

### Initiator

    const initiator = new Initiator();  
     
    // Create a new <video> element  
    const videoElement = document.createElement( "video" );  
    
    // Set the attributes for the video element  
    videoElement.id = "videoChatContainer"; // Set the id so it can be retrieved later  
    videoElement.width = 640; // Set width  
    videoElement.height = 480; // Set height  
    videoElement.autoplay = true; // Autoplay the video (for webcam feed)  
      
    // Append the video element to the body or any other container element
    document.body.appendChild( videoElement );  
      
    initiator.getPeerConnection().addEventListener("track", (event) => {  
      const remoteStream = event.streams[0];  
      videoElement.srcObject = remoteStream;  
    });  
      
    const webcamStream = await getWebcamStream();  
    webcamStream.getTracks().forEach(track => initiator.getPeerConnection().addTrack(track, webcamStream));  
      
    const offer = await initiator.createTrackOffer();

### Receiver

    const receiver = new Receiver();  
      
    // Create a new <video> element  
    const videoElement = document.createElement( "video" );  
    
    // Set the attributes for the video element  
    videoElement.id = "videoChatContainer"; // Set the id so it can be retrieved later  
    videoElement.width = 640; // Set width  
    videoElement.height = 480; // Set height  
    videoElement.autoplay = true; // Autoplay the video (for webcam feed)  
      
    // Append the video element to the body or any other container element
    document.body.appendChild( videoElement );  
      
    receiver.getPeerConnection().addEventListener("track", (event) => {  
      const remoteStream = event.streams[0];  
      videoElement.srcObject = remoteStream;  
    });  
      
    const webcamStream = await getWebcamStream();  
    webcamStream.getTracks().forEach(track => receiver.getPeerConnection().addTrack(track, webcamStream));  
      
    await receiver.processOffer( offer );  
    const answer = receiver.getAnswer();

### Initiator

    await initiator.processAnswer( answer );


The `getWebcamStream` function could look like this:

    const getWebcamStream = async (): Promise<MediaStream> => {  
      try {  
	      // Request access to the webcam and microphone  
	      const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });  
      
	      // Return the stream  
	      return stream;  
      } catch ( error ) {  
	      throw new Error("Unable to access the webcam and microphone.");  
      }  
    };

# Testing

Use Jest to initiate the tests.

    root@root:~# npm install ts-jest

    root@root:~# npx jest