Use PeerJS for peer-to-peer transfers.

pull/1/head
Alex Kern 11 years ago
parent 7532673a88
commit 1870e26e13

@ -0,0 +1,16 @@
import alt from './alt';
export default alt.createActions(class Actions {
constructor() {
this.generateActions(
'download',
'receiveData',
'requestDownload',
'setDownloadInfo',
'sendToDownloader',
'updatePeerID',
'updateToken',
'upload'
)
}
})

@ -3,53 +3,55 @@ import FileDescription from './FileDescription';
import React from 'react'; import React from 'react';
import Tempalink from './Tempalink'; import Tempalink from './Tempalink';
import socket from './socket'; import socket from './socket';
import upload from './upload'; import Actions from './Actions';
import download from './download'; import Store from './Store';
export default class App extends React.Component { export default class App extends React.Component {
constructor() { constructor() {
super(); this.state = Store.getState();
this.state = { token: null, file: null }; this._onChange = function() {
this.setState(Store.getState());
}.bind(this);
var self = this; }
socket.on('token', function (t) {
self.setState({ token: t });
});
socket.on('download', function (t) {
if (self.state.file) upload(self.state.file, t);
});
socket.on('upload', function (data) {
download(window.metadata.name, new Blob([data]));
});
if (window.token) socket.emit('download', window.token); componentDidMount() {
Store.listen(this._onChange);
}
componentDidUnmount() {
Store.unlisten(this._onChange);
} }
useFile(file) { uploadFile(file) {
this.setState({ file: file }); Actions.upload(file);
}
socket.emit('update', { downloadFile() {
name: file.name, Actions.requestDownload();
size: file.size,
type: file.type
});
} }
render() { render() {
if (this.state.file) { if (this.state.readyToUpload) {
return ( return (
<div> <div>
<FileDescription file={this.state.file} /> <FileDescription file={this.state.file} />
<Tempalink token={this.state.token} /> <Tempalink token={this.state.token} />
</div> </div>
); );
} else if (this.state.readyToDownload) {
return (
<div>
<FileDescription file={this.state.downloadMetadata} />
<button onClick={this.downloadFile.bind(this)}>Download</button>
</div>
);
} else { } else {
return <DropZone onDrop={this.useFile.bind(this)} />; return <DropZone onDrop={this.uploadFile.bind(this)} />;
} }
} }
} }

@ -0,0 +1,31 @@
export default class ChunkedBlob {
constructor(n) {
this.n = n;
this.count = n;
this.chunks = [];
}
setChunk(i, b) {
if (i < 0 || i >= this.n)
throw new Error('Chunk out of range');
if (this.chunks[i])
throw new Error('Chunk already set');
this.count--;
this.chunks[i] = b;
}
ready() {
return this.count === 0;
}
toBlob() {
if (!this.ready())
throw new Error('Incomplete blob');
return new Blob(this.chunks);
}
}

@ -0,0 +1,107 @@
import Actions from './Actions';
import alt from './alt';
import peer from './peer';
import socket from './socket';
import ChunkedBlob from './ChunkedBlob';
const chunkSize = 1024;
function downloadFile(name, blob) {
let url = URL.createObjectURL(blob);
let a = document.createElement('a');
a.download = name;
a.href = url;
a.click();
URL.revokeObjectURL(url);
}
export default alt.createStore(class Store {
constructor() {
this.bindActions(Actions);
this.peerID = null;
this.token = null;
this.file = null;
this.readyToUpload = false;
this.downloadToken = null;
this.downloadMetadata = null;
this.downloadChunks = null;
this.readyToDownload = false;
}
updateReadyStatus() {
this.readyToUpload = !!this.peerID && !!this.token && !!this.file;
this.readyToDownload = !!this.peerID && !!this.downloadToken && !!this.downloadMetadata;
}
onUpdatePeerID(id) {
this.peerID = id;
this.updateReadyStatus();
}
onUpdateToken(token) {
this.token = token;
this.updateReadyStatus();
}
onUpload(file) {
this.file = file;
this.updateReadyStatus();
socket.emit('upload', {
name: file.name,
size: file.size,
type: file.type
});
}
onSetDownloadInfo(info) {
this.downloadToken = info.token;
this.downloadMetadata = info.metadata;
this.updateReadyStatus();
}
onRequestDownload() {
if (!this.readyToDownload) return;
socket.emit('download', {
peerID: this.peerID,
token: this.downloadToken
});
}
onSendToDownloader(peerID) {
if (!this.readyToUpload) return;
let file = this.file;
let conn = peer.connect(peerID);
conn.on('open', function () {
let chunks = Math.ceil(file.size / chunkSize);
for (let i = 0; i < chunks; i++) {
let start = i * chunkSize;
let end = start + chunkSize;
let chunk = file.slice(start, end);
conn.send({ n: chunks, i: i, b: chunk });
}
});
}
onReceiveData(data) {
if (!this.readyToDownload) return;
if (!this.downloadChunks)
this.downloadChunks = new ChunkedBlob(data.n);
this.downloadChunks.setChunk(data.i, data.b);
if (this.downloadChunks.ready()) {
let blob = this.downloadChunks.toBlob();
downloadFile(this.downloadMetadata.name, blob);
}
}
})

@ -0,0 +1,2 @@
var Alt = require('alt');
module.exports = new Alt();

@ -1,12 +0,0 @@
export default function(name, blob) {
var reader = new FileReader();
reader.onloadend = function() {
var link = document.createElement('a');
link.download = name;
link.href = reader.result;
link.click();
};
reader.readAsDataURL(blob);
}

@ -1,3 +1,6 @@
import App from './App'; import App from './App';
import React from 'react'; import React from 'react';
import Actions from './Actions';
if (window.WebDrop) Actions.setDownloadInfo(window.WebDrop);
React.render(<App />, document.getElementById('app')); React.render(<App />, document.getElementById('app'));

@ -0,0 +1,15 @@
import Actions from './Actions';
import Peer from 'peerjs';
var peer = module.exports = new Peer({ key: '8w3x9m637e0o1or' });
peer.on('open', function () {
Actions.updatePeerID(peer.id);
if (window.token) Actions.requestDownload(window.token);
});
peer.on('connection', function (conn) {
conn.on('data', function (data) {
Actions.receiveData(data);
});
});

@ -1,2 +1,12 @@
import io from 'socket.io-client'; import io from 'socket.io-client';
export default io.connect('http://localhost:3000'); import Actions from './Actions';
var socket = module.exports = io.connect(window.location.origin);
socket.on('token', function (token) {
Actions.updateToken(token);
});
socket.on('download', function (peerID) {
Actions.sendToDownloader(peerID);
});

@ -1,10 +0,0 @@
import socket from './socket';
export default function (file, token) {
socket.emit('upload', {
token: token,
blob: file
});
}

@ -5,8 +5,8 @@
"main": "server/index.js", "main": "server/index.js",
"scripts": { "scripts": {
"start": "node server/index.js", "start": "node server/index.js",
"build": "./node_modules/.bin/browserify client/index.js -o static/app.js -t babelify -t reactify -d", "build": "./node_modules/.bin/browserify -d -o static/app.js -t babelify -t reactify client/index.js",
"watch": "./node_modules/.bin/watchify client/index.js -o static/app.js -t babelify -t reactify -d" "watch": "./node_modules/.bin/watchify -d -o static/app.js -t babelify -t reactify client/index.js"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -19,9 +19,11 @@
}, },
"homepage": "https://github.com/kern/webdrop", "homepage": "https://github.com/kern/webdrop",
"dependencies": { "dependencies": {
"alt": "^0.14.4",
"bases": "^0.2.1", "bases": "^0.2.1",
"ejs": "^2.3.1", "ejs": "^2.3.1",
"express": "^4.12.0", "express": "^4.12.0",
"peerjs": "^0.3.14",
"socket.io": "^1.3.5" "socket.io": "^1.3.5"
}, },
"devDependencies": { "devDependencies": {

@ -4,7 +4,7 @@ var crypto = require('crypto');
var tokenLength = 8; var tokenLength = 8;
var tokens = {}; var tokens = {};
var Client = module.exports = function (socket) { var Upload = module.exports = function (socket) {
var maxNum = Math.pow(62, tokenLength); var maxNum = Math.pow(62, tokenLength);
var numBytes = Math.ceil(Math.log(maxNum) / Math.log(256)); var numBytes = Math.ceil(Math.log(maxNum) / Math.log(256));
@ -29,14 +29,14 @@ var Client = module.exports = function (socket) {
}; };
Client.exists = function (token) { Upload.exists = function (token) {
return token in tokens; return token in tokens;
}; };
Client.find = function (token) { Upload.find = function (token) {
return tokens[token]; return tokens[token];
}; };
Client.remove = function (client) { Upload.remove = function (client) {
delete tokens[client.token]; delete tokens[client.token];
}; };

@ -1,4 +1,4 @@
var Client = require('./Client'); var Upload = require('./Upload');
var express = require('express'); var express = require('express');
var http = require('http'); var http = require('http');
var path = require('path'); var path = require('path');
@ -22,8 +22,7 @@ app.get('/', function (req, res) {
}); });
app.get('/d/:token', function (req, res) { app.get('/d/:token', function (req, res) {
console.log("Downloading from:", req.params.token); var uploader = Upload.find(req.params.token);
var uploader = Client.find(req.params.token);
res.render('download', { res.render('download', {
token: uploader.token, token: uploader.token,
meta: uploader.metadata meta: uploader.metadata
@ -34,32 +33,21 @@ app.use(express.static(__dirname + '/../static'));
io.on('connection', function (socket) { io.on('connection', function (socket) {
var client = new Client(socket); var upload = null;
socket.emit('token', client.token);
function log(type, data) { socket.on('upload', function (metadata) {
console.log(client.token, '.', type, ':', data); if (!upload) upload = new Upload(socket);
} upload.metadata = metadata;
socket.emit('token', upload.token);
socket.on('upload', function (data) {
var downloader = Client.find(data.token);
downloader.socket.emit('upload', data.blob);
log('upload', data);
});
socket.on('download', function (token) {
var uploader = Client.find(token);
uploader.socket.emit('download', client.token);
log('download', token);
}); });
socket.on('update', function (data) { socket.on('download', function (data) {
client.metadata = data; var uploader = Upload.find(data.token);
log('update', data); uploader.socket.emit('download', data.peerID);
}); });
socket.on('disconnect', function () { socket.on('disconnect', function () {
Client.remove(client); if (upload) Upload.remove(upload);
}); });
}); });

File diff suppressed because one or more lines are too long

@ -12,8 +12,10 @@
<body> <body>
<script> <script>
window.token = <%- JSON.stringify(token) %>; window.WebDrop = {
window.metadata = <%- JSON.stringify(meta) %>; token: <%- JSON.stringify(token) %>,
metadata: <%- JSON.stringify(meta) %>
};
</script> </script>
<div id="app"></div> <div id="app"></div>

Loading…
Cancel
Save