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.