Skip to content

Instantly share code, notes, and snippets.

@johannesMatevosyan
Last active July 16, 2019 07:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save johannesMatevosyan/3c3904d183cf11f0d4a3 to your computer and use it in GitHub Desktop.
Save johannesMatevosyan/3c3904d183cf11f0d4a3 to your computer and use it in GitHub Desktop.
File sharing with WebRTC
// Set up connection between users.
var name, connectedUser;
var connection = new WebSocket('ws://127.0.0.1:8081');
connection.onopen = function () {
console.log("Connected");
};
// Handle all messages through this callback
connection.onmessage = function (message) {
console.log("Got message", message.data);
var data = JSON.parse(message.data);
switch(data.type) {
case "login":
onLogin(data.success);
break;
case "offer":
onOffer(data.offer, data.name);
break;
case "answer":
onAnswer(data.answer);
break;
case "candidate":
onCandidate(data.candidate);
break;
case "leave":
onLeave();
break;
default:
break;
}
};
connection.onerror = function (err) {
console.log("Got error", err);
};
// Alias for sending messages in JSON format
function send(message) {
if (connectedUser) {
message.name = connectedUser;
}
connection.send(JSON.stringify(message)); //serializes a JS object into a JSON string
};
var loginPage = document.querySelector('#login-page'),
usernameInput = document.querySelector('#your-username'),
loginButton = document.querySelector('#login'),
theirUsernameInput = document.querySelector('#their-username'),
connectButton = document.querySelector('#connect'),
sharePage = document.querySelector('#share-page'),
sendButton = document.querySelector('#send'),
readyText = document.querySelector('#ready'),
statusText = document.querySelector('#progress');
sharePage.style.display = "none";
readyText.style.display = "none";
// Login when the user clicks the button
loginButton.addEventListener("click", function (event) {
name = usernameInput.value;
if (name.length > 0) {
send({
type: "login",
name: name
});
}
});
function onLogin(success) {
if (success === false) {
alert("Login unsuccessful, please try a different name.");
} else {
loginPage.style.display = "none";
sharePage.style.display = "block";
// if login was sucessful then setup connection
startConnection();
}
};
var yourConnection, connectedUser, dataChannel, currentFile, currentFileSize, currentFileMeta;
function startConnection() {
if (hasRTCPeerConnection()) {
setupPeerConnection();
} else {
alert("Sorry, your browser does not support WebRTC.");
}
}
function setupPeerConnection() {
var configuration = {
"iceServers": [{ "url": "stun:127.0.0.1:9876" }]
};
yourConnection = new RTCPeerConnection(configuration, {optional: []});
// Setup ice handling
yourConnection.onicecandidate = function (event) {
if (event.candidate) {
send({
type: "candidate",
candidate: event.candidate
});
}
};
openDataChannel();
}
function openDataChannel() {
var dataChannelOptions = {
ordered: true,
reliable: true,
negotiated: true,
id: "myChannel"
};
dataChannel = yourConnection.createDataChannel("myLabel", dataChannelOptions);
dataChannel.onerror = function (error) {
console.log("Data Channel Error:", error);
};
dataChannel.onmessage = function (event) {
try {
// The JSON.parse API throws an error because it is unable
// to decode the data received from file
var message = JSON.parse(event.data); // deserialize a JSON string into a JS object
switch (message.type) {
case "start":
currentFile = [];
currentFileSize = 0;
currentFileMeta = message.data;
console.log("Receiving file -> ", currentFileMeta);
break;
case "end":
saveFile(currentFileMeta, currentFile);
break;
}
} catch (e) {
// Assume this is file content
currentFile.push(atob(event.data));
currentFileSize += currentFile[currentFile.length - 1].length;
var percentage = Math.floor((currentFileSize / currentFileMeta.size) * 100);
statusText.innerHTML = "Receiving... " + percentage + "%";
}
};
dataChannel.onopen = function () {
readyText.style.display = "inline-block";
};
dataChannel.onclose = function () {
readyText.style.display = "none";
};
}
// Alias for sending messages in JSON format
function dataChannelSend(message) {
dataChannel.send(JSON.stringify(message));
}
function saveFile(meta, data) {
var blob = base64ToBlob(data, meta.type);
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = meta.name;
link.click();
}
function hasUserMedia() { // define environment to set prefixes fo getUserMedia
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
return !!navigator.getUserMedia;
}
function hasRTCPeerConnection() { // define environment to setup prefixes for RTCPeerConnection
window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
window.RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription;
window.RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate || window.mozRTCIceCandidate;
return !!window.RTCPeerConnection;
}
function hasFileApi() {
return window.File && window.FileReader && window.FileList && window.Blob;
}
connectButton.addEventListener("click", function () {
var theirUsername = theirUsernameInput.value;
if (theirUsername.length > 0) {
startPeerConnection(theirUsername);
}
});
function startPeerConnection(user) {
connectedUser = user;
// Begin the offer
yourConnection.createOffer(function (offer) {
send({
type: "offer",
offer: offer
});
yourConnection.setLocalDescription(offer);
}, function (error) {
alert("An error has occurred in startPeerConnection()");
});
};
function onOffer(offer, name) {
connectedUser = name;
yourConnection.setRemoteDescription(new RTCSessionDescription(offer));
yourConnection.createAnswer(function (answer) {
yourConnection.setLocalDescription(answer);
send({
type: "answer",
answer: answer
});
}, function (error) { // error callback is called while trying to establish connection
alert("An error has occurred in onOffer()");
});
};
function onAnswer(answer) {
yourConnection.setRemoteDescription(new RTCSessionDescription(answer));
};
function onCandidate(candidate) {
yourConnection.addIceCandidate(new RTCIceCandidate(candidate));
};
function onLeave() {
connectedUser = null;
yourConnection.close();
yourConnection.onicecandidate = null;
setupPeerConnection();
};
function arrayBufferToBase64(buffer) {
var binary = '';
var bytes = new Uint8Array( buffer );
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode( bytes[ i ] );
}
return btoa(binary);
}
function base64ToBlob(b64Data, contentType) {
contentType = contentType || '';
var byteArrays = [], byteNumbers, slice;
for (var i = 0; i < b64Data.length; i++) {
slice = b64Data[i];
byteNumbers = new Array(slice.length);
for (var n = 0; n < slice.length; n++) {
byteNumbers[n] = slice.charCodeAt(n);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
var blob = new Blob(byteArrays, {type: contentType});
return blob;
}
var CHUNK_MAX = 16000;
function sendFile(file) {
var reader = new FileReader();
reader.onloadend = function(evt) {
if (evt.target.readyState == FileReader.DONE) {
var buffer = reader.result,
start = 0,
end = 0,
last = false;
// Creating a recursive function that we can send pieces of the file with.
function sendChunk() {
end = start + CHUNK_MAX;
if (end > file.size) {
end = file.size;
last = true;
}
var percentage = Math.floor((end / file.size) * 100);
statusText.innerHTML = "Sending... " + percentage + "%";
dataChannel.send(arrayBufferToBase64(buffer.slice(start, end)));
// If this is the last chunk send our end message, otherwise keep sending
if (last === true) {
dataChannelSend({
type: "end"
});
} else {
start = end;
// Throttle the sending to avoid flooding
setTimeout(function () {
sendChunk();
}, 100);
}
}
sendChunk();
}
};
reader.readAsArrayBuffer(file);
}
// click send button to transfer uploaded file.
sendButton.addEventListener("click", function (event) {
var files = document.querySelector('#files').files;
if (files.length > 0) {
dataChannelSend({
type: "start",
data: files[0]
});
sendFile(files[0]);
}
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Learning WebRTC - Chapter 7: File Sharing</title>
<style>
body {
background-color: #406060;
margin-top: 10px;
font-family: sans-serif;
color: white;
}
.thumb {
height: 75px;
border: 1px solid #000;
margin: 10px 5px 0 0;
}
.page {
position: relative;
display: block;
margin: 0 auto;
width: 500px;
height: 500px;
}
#byte_content {
margin: 5px 0;
max-height: 100px;
overflow-y: auto;
overflow-x: hidden;
}
#byte_range {
margin-top: 5px;
}
</style>
</head>
<body>
<div id="login-page" class="page">
<h2>Login As</h2>
<input type="text" id="your-username" />
<button id="login">Login</button>
</div>
<div id="share-page" class="page">
<h2>File Sharing</h2>
<input type="text" id="their-username" />
<button id="connect">Connect</button>
<div id="ready">Ready!</div>
<br />
<input type="file" id="files" name="file" />
<button id="send">Send</button>
<br />
<div id="progress"></div>
</div>
<script src="client.js"></script>
</body>
</html>
var WebSocketServer = new require('ws');
// connected users
var clients = {};
// WebSocket-server is on 8081 port
var webSocketServer = new WebSocketServer.Server({port: 8081});
webSocketServer.on('connection', function(ws) {
var id = Math.random();
clients[id] = ws;
console.log("new connection " + id);
ws.on('message', function(message) {
console.log('message received ' + message);
for(var key in clients) {
clients[key].send(message);
}
});
ws.on('close', function() {
console.log('connection closed' + id);
delete clients[id];
});
});
console.log("Server started at ports 8081");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment