How to build a file transfer application with node and webRTC

How to build a file transfer application with node and webRTC

ยท

4 min read

Hello there, In this article, we would be building a file transfer application using webRTC and node.js. This app would let users transfer heavy files to each other without the need for a server.

Note, in order to proceed with the tutorial, I would highly recommend you to please read my previous tutorial on webRTC Build a video chat app with webRTC.

Alright, let's begin.

Setting up the server for signaling.

In this very step, we are going to set up a simple signaling server using node and socket.io. I won't be explaining the signaling process as I have already that in Build a video chat app with webRTC article. If you haven't read that, please go and read.

// signaling server

const http = require('http');
const express = require('express');
const { Server: SocketIO } = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = new SocketIO(server, { cors: true });

const PORT = process.env.PORT || 8000;

app.use(express.static('./public'));

io.on('connection', socket => {

    socket.emit('me', socket.id);

    socket.on('make:offer', data => {
        const { offer, to } = data;
        socket.to(to).emit('incomming:offer', { offer, from: socket.id });
    });

    socket.on('make:answer', data => {
        const { answer, to } = data;
        socket.to(to).emit('incomming:answer', { answer, from: socket.id });
    });
});

server.listen(PORT, () => console.log(`๐Ÿš€ Server started at PORT${PORT}`));

Very basic, no rocket science. I hope that the above code is clear to you.

Great, now let's move further to our front-end part.

Let's start with a basic boiler plate code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h3 id="my-id"></h3>

    <form id="connect">
        <input type="text" id="id" />
        <button type="submit">SEND</button>
    </form>

    <form id="file-transfer">
        <input id="file" type="file" />
        <button type="submit">SEND</button>
    </form>

    <script src="/socket.io/socket.io.js"></script>
</body>
</html>

Pretty simple.

Now let's define some global variables.

const form = document.getElementById('file-transfer');
const connectForm = document.getElementById('connect');

const socket = io();
const peer = new RTCPeerConnection();
let dataChannel;

Alright, good going ๐Ÿš€

Now let's add some event listeners to our form and socket.

socket.on('me', id => document.getElementById('my-id').innerText = id); // to get our own socket id.

form.addEventListener('submit', async (ev) => {
    ev.preventDefault();
});

connectForm.addEventListener('submit', async (ev) => {
    ev.preventDefault();
});

Alright, now let's focus on our connection process.

So, what we would be doing is, when a user submits the connection form, we are going to init the signaling process and connect both the peers.

connectForm.addEventListener('submit', async (ev) => {
    ev.preventDefault();
    const remoteId = document.getElementById('id').value;

    const offer = await peer.createOffer();
    await peer.setLocalDescription(new RTCSessionDescription(offer));

    // This dataChanel we are going to use to send data
    dataChannel = peer.createDataChannel('file-transfer');
    socket.emit('make:offer', { offer, to: remoteId });
});

and now, create an event listener for the incoming offer

socket.on('incomming:offer', async data => {
    await peer.setRemoteDescription(new RTCSessionDescription(data.offer));
    const answer = await peer.createAnswer();
    await peer.setLocalDescription(new RTCSessionDescription(answer));
    socket.emit('make:answer', { answer, to: data.from })

    // Init the remote datachannel as soon as the first peer creates a data channel
    peer.addEventListener('datachannel', ev => {
        dataChannel = ev.channel
        dataChannel.send('Hello Peer');
    });
})

and finally, the incoming answer

socket.on('incomming:answer', async data => {
    await peer.setRemoteDescription(new RTCSessionDescription(data.answer));
})

Now let's complete our file transfer form listener where are actually going to transfer the files.

form.addEventListener('submit', async (ev) => {
    ev.preventDefault();

    // Get the file from input field.
    const file = document.getElementById('file').files[0];
    // convert the file to buffer
    const fileBuffer = await file.arrayBuffer();

    // finally, send it to the peer data channel
    dataChannel.send(fileBuffer);
});

Great, so on the receiver side, listen for this file and download the file to the client.

connectForm.addEventListener('submit', async (ev) => {
    ev.preventDefault();
    const remoteId = document.getElementById('id').value;

    const offer = await peer.createOffer();
    await peer.setLocalDescription(new RTCSessionDescription(offer));

    // This dataChanel we are going to use to send data
    dataChannel = peer.createDataChannel('file-transfer');
    socket.emit('make:offer', { offer, to: id });

    // Adding a message listener
    dataChannel.addEventListener('message', ev => {

        if (typeof ev.data == 'object') {
            const a = document.createElement('a');
            const blob = new Blob([ev.data]);
            const obj = URL.createObjectURL(blob);
            a.href = obj;
            a.download = 'rec.png';
            a.click()
        }     
    });
});

And that's it, the file would get downloaded to the client's machine.

Did you find this article valuable?

Support Piyush Garg by becoming a sponsor. Any amount is appreciated!

ย