diff --git a/lib/ChunkedBlob.js b/lib/ChunkedBlob.js deleted file mode 100644 index 0ceb2cc..0000000 --- a/lib/ChunkedBlob.js +++ /dev/null @@ -1,25 +0,0 @@ -const rankSize = 16 - -function blobLength(b) { - if (typeof b.byteLength !== 'undefined') return b.byteLength - if (typeof b.size !== 'undefined') return b.size - return b.length -} - -export default class ChunkedBlob { - - constructor() { - this.size = 0 - this.ranks = [] - } - - add(b) { - this.size += blobLength(b) - this.ranks.push(b) - } - - toBlob() { - return new Blob(this.ranks) - } - -} \ No newline at end of file diff --git a/lib/DownloadFile.js b/lib/DownloadFile.js deleted file mode 100644 index 38492c3..0000000 --- a/lib/DownloadFile.js +++ /dev/null @@ -1,54 +0,0 @@ -import ChunkedBlob from './ChunkedBlob' - -export default class DownloadFile { - - constructor(name, size, type) { - this.name = name - this.size = size - this.type = type - this.chunks = new ChunkedBlob() - } - - addChunk(b) { - this.chunks.add(b) - } - - clearChunks() { - this.chunks = new ChunkedBlob() - } - - isComplete() { - return this.getProgress() === 1 - } - - getProgress() { - return this.chunks.size / this.size - } - - download() { - - let blob = this.chunks.toBlob() - let url = URL.createObjectURL(blob) - - let a = document.createElement('a') - document.body.appendChild(a) - a.download = this.name - a.href = url - a.click() - - setTimeout(() => { - URL.revokeObjectURL(url) - document.body.removeChild(a) - }, 0) - - } - - toJSON() { - return { - name: this.name, - size: this.size, - type: this.type - } - } - -} diff --git a/lib/UploadFile.js b/lib/UploadFile.js deleted file mode 100644 index d6cb0f1..0000000 --- a/lib/UploadFile.js +++ /dev/null @@ -1,33 +0,0 @@ -const chunkSize = 256 * 1024 - -export default class UploadFile { - - constructor(file) { - this.name = file.name - this.size = file.size - this.type = file.type - this.blob = file - } - - countChunks() { - return Math.ceil(this.size / chunkSize) - } - - getChunk(i) { - if (i < 0 || i >= this.countChunks()) - throw new Error('Chunk out of bounds') - - let start = i * chunkSize - let end = Math.min(start + chunkSize, this.size) - return this.blob.slice(start, end) - } - - toJSON() { - return { - name: this.name, - size: this.size, - type: this.type - } - } - -} diff --git a/lib/actions/DownloadActions.js b/lib/actions/DownloadActions.js index 9cdfe8e..de2dcdf 100644 --- a/lib/actions/DownloadActions.js +++ b/lib/actions/DownloadActions.js @@ -3,8 +3,7 @@ import alt from '../alt' export default alt.createActions(class DownloadActions { constructor() { this.generateActions( - 'requestDownload', - 'beginDownload' + 'requestDownload' ) } }) diff --git a/lib/actions/UploadActions.js b/lib/actions/UploadActions.js index 42a3099..ae03df5 100644 --- a/lib/actions/UploadActions.js +++ b/lib/actions/UploadActions.js @@ -3,8 +3,6 @@ import alt from '../alt' export default alt.createActions(class UploadActions { constructor() { this.generateActions( - 'sendToDownloader', - 'setUploadToken', 'uploadFile' ) } diff --git a/lib/components/ChromeNotice.js b/lib/components/ChromeNotice.js index 692d9c0..cc5fd66 100644 --- a/lib/components/ChromeNotice.js +++ b/lib/components/ChromeNotice.js @@ -4,7 +4,7 @@ import SupportStore from '../stores/SupportStore' function getState() { return { - active: SupportStore.getState().isChrome && DownloadStore.getState().file.size >= 500000000 + active: SupportStore.getState().isChrome && DownloadStore.getState().fileSize >= 500000000 } } diff --git a/lib/components/DownloadPage.js b/lib/components/DownloadPage.js index a42ac2a..840417e 100644 --- a/lib/components/DownloadPage.js +++ b/lib/components/DownloadPage.js @@ -2,10 +2,10 @@ import ChromeNotice from './ChromeNotice' import DownloadActions from '../actions/DownloadActions' import DownloadButton from './DownloadButton' import DownloadStore from '../stores/DownloadStore' +import ErrorPage from './ErrorPage' import ProgressBar from './ProgressBar' import React from 'react' import Spinner from './Spinner' -import peer from 'filepizza-peerjs' export default class DownloadPage extends React.Component { @@ -16,20 +16,14 @@ export default class DownloadPage extends React.Component { this._onChange = () => { this.setState(DownloadStore.getState()) } - - this._onConnection = (conn) => { - DownloadActions.beginDownload(conn) - } } componentDidMount() { DownloadStore.listen(this._onChange) - peer.on('connection', this._onConnection) } componentDidUnmount() { DownloadStore.unlisten(this._onChange) - peer.removeListener('connection', this._onConnection) } downloadFile() { @@ -43,8 +37,8 @@ export default class DownloadPage extends React.Component {

FilePizza

+ name={this.state.fileName} + size={this.state.fileSize} /> @@ -57,39 +51,29 @@ export default class DownloadPage extends React.Component {

FilePizza

+ name={this.state.fileName} + size={this.state.fileSize} /> - case 'cancelled': - return
- -

FilePizza

- - - - - -
- case 'done': return

FilePizza

+ name={this.state.fileName} + size={this.state.fileSize} />
+ + default: + return } } diff --git a/lib/components/UploadPage.js b/lib/components/UploadPage.js index d7a491c..3d193e3 100644 --- a/lib/components/UploadPage.js +++ b/lib/components/UploadPage.js @@ -1,5 +1,4 @@ import DropZone from './DropZone' -import ProgressBar from './ProgressBar' import React from 'react' import Spinner from './Spinner' import Tempalink from './Tempalink' @@ -12,46 +11,40 @@ export default class UploadPage extends React.Component { constructor() { super() this.state = UploadStore.getState() - + this._onChange = () => { this.setState(UploadStore.getState()) } - - this._onDownload = (peerID) => { - UploadActions.sendToDownloader(peerID) - } } - + componentDidMount() { UploadStore.listen(this._onChange) - socket.on('download', this._onDownload) } - + componentDidUnmount() { UploadStore.unlisten(this._onChange) - socket.removeListener('download', this._onDownload) } - + uploadFile(file) { UploadActions.uploadFile(file) } - + handleSelectedFile(event) { - let files = event.target.files; + let files = event.target.files if (files.length > 0) { - UploadActions.uploadFile(files[0]); + UploadActions.uploadFile(files[0]) } } - + render() { switch (this.state.status) { case 'ready': - + return
- + - +

FilePizza

Free peer-to-peer file transfers in your browser.

We never store anything. Files only served fresh.

@@ -63,36 +56,29 @@ export default class UploadPage extends React.Component {

- + case 'processing': return
- + - +

FilePizza

Processing...

- +
- + case 'uploading': - var keys = Object.keys(this.state.peerProgress) - keys.reverse() return
- +

FilePizza

- - + +

Send someone this link to download.

This link will work as long as this page is open.

- - {keys.length > 0 ?

Download Progress

: null} -
- {keys.map((key) => { - return - })} -
- +
} } diff --git a/lib/db.js b/lib/db.js index 6429096..245929c 100644 --- a/lib/db.js +++ b/lib/db.js @@ -35,5 +35,6 @@ export function find(token) { } export function remove(client) { + if (client == null) return delete tokens[client.token] } diff --git a/lib/middleware/bootstrap.js b/lib/middleware/bootstrap.js index 505b629..68f93cb 100644 --- a/lib/middleware/bootstrap.js +++ b/lib/middleware/bootstrap.js @@ -11,7 +11,10 @@ routes.get(/^\/([a-z]+-[a-z]+-[a-z]+-[a-z]+)$/, function (req, res, next) { DownloadStore: { status: 'ready', token: uploader.token, - file: uploader.metadata + fileSize: uploader.fileSize, + fileName: uploader.fileName, + fileType: uploader.fileType, + infoHash: uploader.infoHash } } diff --git a/lib/middleware/react.js b/lib/middleware/react.js index eefaea9..abab556 100644 --- a/lib/middleware/react.js +++ b/lib/middleware/react.js @@ -14,16 +14,16 @@ function isNotFound(state) { module.exports = function (req, res) { alt.bootstrap(JSON.stringify(res.locals.data || {})) - + ReactRouter.run(routes, req.url, function (Handler, state) { - + var html = React.renderToString() alt.flush() - + if (isNotFound(state)) res.status(404) res.write('\n') res.end(html) - + }) } diff --git a/lib/routes.js b/lib/routes.js index c5a1aa0..5e5e2e7 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -14,3 +14,4 @@ export default ( ) + diff --git a/lib/server.js b/lib/server.js index 7e99ac8..25da942 100644 --- a/lib/server.js +++ b/lib/server.js @@ -3,7 +3,6 @@ var express = require('express') var fs = require('fs') var http = require('http') var path = require('path') -var peer = require('peer') var socketIO = require('socket.io') var winston = require('winston') var expressWinston = require('express-winston') @@ -32,8 +31,6 @@ server.listen(process.env.PORT || 3000, function () { console.log('FilePizza listening on %s:%s', host, port) }) -app.use('/peer', peer.ExpressPeerServer(server)) - app.use(expressWinston.logger({ winstonInstance: winston, expressFormat: true @@ -55,22 +52,18 @@ io.on('connection', function (socket) { socket.on('upload', function (metadata, res) { if (upload) return - upload = true db.create(socket).then((u) => { upload = u - upload.metadata = metadata + upload.fileName = metadata.fileName + upload.fileSize = metadata.fileSize + upload.fileType = metadata.fileType + upload.infoHash = metadata.infoHash res(upload.token) }) }) - socket.on('download', function (data) { - var uploader = db.find(data.token) - if (!uploader) return - uploader.socket.emit('download', data.peerID) - }) - socket.on('disconnect', function () { - if (upload) db.remove(upload) + db.remove(upload) }) }) diff --git a/lib/stores/DownloadStore.js b/lib/stores/DownloadStore.js index b3285cf..362481b 100644 --- a/lib/stores/DownloadStore.js +++ b/lib/stores/DownloadStore.js @@ -1,62 +1,57 @@ import DownloadActions from '../actions/DownloadActions' -import DownloadFile from '../DownloadFile' -import peer from 'filepizza-peerjs' +import WebTorrent from 'webtorrent' import alt from '../alt' import socket from 'filepizza-socket' +function downloadBlobURL(name, blobURL) { + let a = document.createElement('a') + document.body.appendChild(a) + a.download = name + a.href = blobURL + a.click() +} + export default alt.createStore(class DownloadStore { constructor() { this.bindActions(DownloadActions) - this.status = 'ready' - this.token = null - this.file = null + this.fileName = '' + this.fileSize = 0 + this.fileType = '' this.progress = 0 - - this.on('bootstrap', () => { - if (this.file && !(this.file instanceof DownloadFile)) { - this.file = new DownloadFile(this.file.name, - this.file.size, - this.file.type) - } - }) + this.status = 'uninitialized' + this.token = null + this.infoHash = null } onRequestDownload() { if (this.status !== 'ready') return this.status = 'requesting' - socket.emit('download', { - peerID: peer.id, - token: this.token - }) - } - - onBeginDownload(conn) { - if (this.status !== 'requesting') return - this.status = 'downloading' - - conn.on('data', (chunk) => { - if (this.status !== 'downloading') return - - this.file.addChunk(chunk) - - if (this.file.isComplete()) { - this.setState({ status: 'done', progress: 1 }) - this.file.download() - conn.close() - } else { - this.setState({ progress: this.file.getProgress() }) - conn.send('more') - } - - }) - - conn.on('close', () => { - if (this.status !== 'downloading') return - this.setState({ status: 'cancelled', progress: 0 }) - this.file.clearChunks() + const client = new WebTorrent() + client.download(this.infoHash, (torrent) => { + this.setState({ status: 'downloading' }) + + let downloaded = 0 + const file = torrent.files[0] + const stream = file.createReadStream() + stream.on('data', (chunk) => { + if (this.status !== 'downloading') return + + downloaded += chunk.length + + if (downloaded === file.length) { + this.setState({ status: 'done', progress: 1 }) + file.getBlobURL((err, blobURL) => { + if (err) throw err + downloadBlobURL(this.fileName, blobURL) + }) + } else { + this.setState({ progress: downloaded / file.length }) + } + + }) }) } diff --git a/lib/stores/UploadStore.js b/lib/stores/UploadStore.js index 9f64e25..8012099 100644 --- a/lib/stores/UploadStore.js +++ b/lib/stores/UploadStore.js @@ -1,71 +1,43 @@ import UploadActions from '../actions/UploadActions' -import UploadFile from '../UploadFile' import alt from '../alt' -import peer from 'filepizza-peerjs' import socket from 'filepizza-socket' +import WebTorrent from 'webtorrent' export default alt.createStore(class UploadStore { constructor() { this.bindActions(UploadActions) + this.fileName = '' + this.fileSize = 0 + this.fileType = '' this.status = 'ready' this.token = null - this.file = null - this.peerProgress = {} + this.infoHash = null } onUploadFile(file) { if (this.status !== 'ready') return this.status = 'processing' - this.file = new UploadFile(file) - socket.emit('upload', { - name: this.file.name, - size: this.file.size, - type: this.file.type - }, (token) => { - this.setState({ - status: 'uploading', - token: token + const client = new WebTorrent() + client.seed(file, (torrent) => { + socket.emit('upload', { + fileName: file.name, + fileSize: file.size, + fileType: file.type, + infoHash: torrent.infoHash + }, (token) => { + this.setState({ + status: 'uploading', + token: token, + fileName: file.name, + fileSize: file.size, + fileType: file.type, + infoHash: torrent.infoHash + }) }) }) } - onSendToDownloader(peerID) { - if (this.status !== 'uploading') return - - let conn = peer.connect(peerID, { - reliable: true - }) - - let totalChunks = this.file.countChunks() - let i = 0 - - let sendNextChunk = () => { - if (i === totalChunks) return - let packet = this.file.getChunk(i) - conn.send(packet) - i++ - this.peerProgress[peerID] = i/totalChunks - } - - conn.on('open', () => { - sendNextChunk() - this.setState({ peerProgress: this.peerProgress }) - }) - - conn.on('data', (data) => { - if (data === 'more') sendNextChunk() - this.setState({ peerProgress: this.peerProgress }) - }) - - conn.on('close', () => { - if (this.peerProgress[peerID] < 1) { - this.peerProgress[peerID] = -1 - } - this.setState({ peerProgress: this.peerProgress }) - }) - } - }, 'UploadStore') diff --git a/package.json b/package.json index ffc8dd6..e08ea3d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "filepizza", - "version": "0.1.2", + "version": "0.2.0", "description": "Free peer-to-peer file transfers in your browser.", "main": "index.js", "scripts": { @@ -25,13 +25,10 @@ "classnames": "^1.2.0", "express": "^4.12.0", "express-winston": "^0.3.1", - "filepizza-peerjs": "^1.0.0", "filepizza-socket": "^1.0.0", "newrelic": "^1.21.1", "nib": "^1.1.0", "node-uuid": "^1.4.3", - "peer": "^0.2.8", - "peerjs": "^0.3.14", "react": "^0.13.0", "react-frozenhead": "^0.3.0", "react-google-analytics": "^0.2.0", @@ -40,6 +37,7 @@ "socket.io-client": "^1.3.5", "stylus": "^0.50.0", "webrtcsupport": "^2.1.2", + "webtorrent": "^0.56.0", "winston": "^1.0.1", "xkcd-password": "^1.2.0" },