Last active
July 16, 2019 07:29
-
-
Save johannesMatevosyan/3c3904d183cf11f0d4a3 to your computer and use it in GitHub Desktop.
File sharing with WebRTC
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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]); | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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