Introduction

WebRTC is a protocol that allows to add very advanced capabilities to our JavaScript applications running in the browser.

The technology lets us directly connect two or more browsers to each other, without the need of a centralized service.

We already saw how to create a webcam sharing application using this protocol, and in this tutorial we’ll see how to send files from one peer to another. We’ll send images from one browser to another.

The PeerJS library, which builds on top of WebRTC, provides a great abstraction level over the lower APIs of WebRTC, and we’ll use that in this tutorial.

Suggestion: Tools like Bit can help you share JavaScript components from and between your applications, so you can build more apps faster. Give it a try…

Set up PeerJS

First thing we need to do is to install and run the backend that will serve as a synchronization service for our application.

In a folder, initialize an npm project using npm init , install PeerJS using npm install peer and then run it using npx :

npx peerjs --port 9000

You can see all the options that PeerJS offers by running npx peerjs --help , but for the time being we can run with the defaults. Our backend ends here!

Now off to the frontend. We’ll create 2 files. One for the sending part, another for the receiving part.

The sending end

Let’s start by adding the HTML needed to send the file. We simply create a form with an input element of type file :

<form>

<input type="file" />

</form>

I only want to allow to send images through the wire, and we can do so by adding the accept attribute:

<form>

<input type="file" accept="image/*" />

</form>

image/* represents the MIME type of any image. The browser will only accept images in the file form, but note that this attribute is not supported by all browsers (most notably, not by Edge).

We start the JavaScript part by adding a DOMContentLoaded event listener:

document.addEventListener('DOMContentLoaded', event => { })

In here, we connect to the PeerJS backend, which is running on localhost port 9000, with the sender username, and we use the connect() method to connect to the peer called receiver :

document.addEventListener('DOMContentLoaded', event => {

const peer = new Peer('sender', { host: 'localhost', port: 9000, path: '/' }) const conn = peer.connect('receiver')

})

Next, we listen on the onchange event of the form, called when the user adds a file, and we grab the file that’s been uploaded. We only allow one file at a time for simplicity:

document.addEventListener('DOMContentLoaded', event => {

const peer = new Peer('sender', { host: 'localhost', port: 9000, path: '/' }) const conn = peer.connect('receiver') document.querySelector('input').onchange = function(event) {

const file = event.target.files[0]

const blob = new Blob(event.target.files, { type: file.type })

}

})

Once we have the file, we can send it to the receiver using the send() method:

conn.send({

file: blob,

filename: file.name,

filetype: file.type

})

This is the full code of the sender:

The receiving end

Let’s go into the receiving end of the spectrum. We use addEventListener() event to listen for the DOMContentLoaded event. We initialize the peer object by calling the Peer() constructor:

document.addEventListener('DOMContentLoaded', event => {

const peer = new Peer('receiver', {

host: 'localhost',

port: 9000,

path: '/'

})

})

then we add an event listener on it for the connection event. When the connection happens, we listen for the data event on the connection, which is fired when the other end of the connection calls send() to us.

In here, we receive a data object that contains the information sent, in this case the file details:

document.addEventListener('DOMContentLoaded', event => {

const peer = new Peer('receiver', {

host: 'localhost',

port: 9000,

path: '/'

}) peer.on('connection', conn => {

conn.on('data', data => { })

})

})

What we want to do now is to display the image the sender gave us in an img tag in the page. We add a div element in the HTML, and we use that to push all the images.

We must first create an array of bytes from the file property, using Uint8Array :

const bytes = new Uint8Array(data.file)

Then we create an img tag, and as the src attribute we use the base64 representation of that data:

const img = document.createElement('img')

img.src = 'data:image/png;base64,' + encode(bytes)

encode() is a custom function that allows us to correctly convert the image data to base64.

Here is its code, which I adapted from a StackOverflow question:

const encode = input => {

const keyStr =

'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='

let output = ''

let chr1, chr2, chr3, enc1, enc2, enc3, enc4

let i = 0 while (i < input.length) {

chr1 = input[i++]

chr2 = i < input.length ? input[i++] : Number.NaN // Not sure if the index

chr3 = i < input.length ? input[i++] : Number.NaN // checks are needed here enc1 = chr1 >> 2

enc2 = ((chr1 & 3) << 4) | (chr2 >> 4)

enc3 = ((chr2 & 15) << 2) | (chr3 >> 6)

enc4 = chr3 & 63 if (isNaN(chr2)) {

enc3 = enc4 = 64

} else if (isNaN(chr3)) {

enc4 = 64

}

output +=

keyStr.charAt(enc1) +

keyStr.charAt(enc2) +

keyStr.charAt(enc3) +

keyStr.charAt(enc4)

}

return output

}

Finally we can add the element to the DOM, by using the prepend() method on the div so the image shows up at the top:

document.querySelector('div').prepend(img)

One last thing, we can’t be sure that the client actually sent us an image, so we need to look up the file type before adding the image to the DOM, by wrapping all the code in an if that checks if the filetype property contains the image word:

if (data.filetype.includes('image')) {

}

This is the full code of the receiver:

Wrapping up

The application should now be fully working, and you are able to select files (or drag and drop them in the file input element) and the receiving end will get them, all without needing a centralized server.