From ab8b0a42c1337866b19041f48f9b7e244a9f908e Mon Sep 17 00:00:00 2001 From: Feross Aboukhadijeh Date: Sat, 2 Jan 2016 16:23:30 +0100 Subject: [PATCH] use instant.io as starting point --- client/index.js | 207 ++++++++++++++++++++++++++++++++++++++++++++++++ client/util.js | 38 +++++++++ index.css | 116 ++++++++++++++++++++++----- index.html | 30 +++++-- package.json | 8 +- 5 files changed, 371 insertions(+), 28 deletions(-) create mode 100644 client/index.js create mode 100644 client/util.js diff --git a/client/index.js b/client/index.js new file mode 100644 index 00000000..e709ffe4 --- /dev/null +++ b/client/index.js @@ -0,0 +1,207 @@ +var debug = require('debug')('instant.io') +var dragDrop = require('drag-drop') +// var listify = require('listify') +var path = require('path') +var prettyBytes = require('pretty-bytes') +var thunky = require('thunky') +var uploadElement = require('upload-element') +var WebTorrent = require('webtorrent') +var xhr = require('xhr') + +var util = require('./util') + +global.WEBTORRENT_ANNOUNCE = [ 'wss://tracker.webtorrent.io', 'wss://tracker.btorrent.xyz' ] + +var getClient = thunky(function (cb) { + getRtcConfig('/rtcConfig', function (err, rtcConfig) { + if (err && window.location.hostname === 'instant.io') { + if (err) util.error(err) + createClient(rtcConfig) + } else if (err) { + getRtcConfig('https://instant.io/rtcConfig', function (err, rtcConfig) { + if (err) util.error(err) + createClient(rtcConfig) + }) + } else { + createClient(rtcConfig) + } + }) + + function createClient (rtcConfig) { + var client = window.client = new WebTorrent({ rtcConfig: rtcConfig }) + client.on('warning', util.warning) + client.on('error', util.error) + cb(null, client) + } +}) + +// For performance, create the client immediately +getClient(function () {}) + +// Seed via upload input element +var upload = document.querySelector('input[name=upload]') +uploadElement(upload, function (err, files) { + if (err) return util.error(err) + files = files.map(function (file) { return file.file }) + onFiles(files) +}) + +// Seed via drag-and-drop +dragDrop('body', onFiles) + +// Download via input element +document.querySelector('form').addEventListener('submit', function (e) { + e.preventDefault() + downloadTorrent(document.querySelector('form input[name=torrentId]').value) +}) + +// Download by URL hash +onHashChange() +window.addEventListener('hashchange', onHashChange) +function onHashChange () { + var hash = decodeURIComponent(window.location.hash.substring(1)).trim() + if (hash !== '') downloadTorrent(hash) +} + +// Warn when leaving and there are no other peers +// window.addEventListener('beforeunload', onBeforeUnload) + +// Register a protocol handler for "magnet:" (will prompt the user) +// navigator.registerProtocolHandler('magnet', window.location.origin + '#%s', 'Instant.io') + +function getRtcConfig (url, cb) { + xhr(url, function (err, res) { + if (err || res.statusCode !== 200) { + cb(new Error('Could not get WebRTC config from server. Using default (without TURN).')) + } else { + var rtcConfig + try { + rtcConfig = JSON.parse(res.body) + } catch (err) { + return cb(new Error('Got invalid WebRTC config from server: ' + res.body)) + } + debug('got rtc config: %o', rtcConfig) + cb(null, rtcConfig) + } + }) +} + +function onFiles (files) { + debug('got files:') + files.forEach(function (file) { + debug(' - %s (%s bytes)', file.name, file.size) + }) + + // .torrent file = start downloading the torrent + files.filter(isTorrentFile).forEach(downloadTorrentFile) + + // everything else = seed these files + seed(files.filter(isNotTorrentFile)) +} + +function isTorrentFile (file) { + var extname = path.extname(file.name).toLowerCase() + return extname === '.torrent' +} + +function isNotTorrentFile (file) { + return !isTorrentFile(file) +} + +function downloadTorrent (torrentId) { + util.log('Downloading torrent from ' + torrentId) + getClient(function (err, client) { + if (err) return util.error(err) + client.add(torrentId, onTorrent) + }) +} + +function downloadTorrentFile (file) { + util.log('Downloading torrent from ' + file.name + '') + getClient(function (err, client) { + if (err) return util.error(err) + client.add(file, onTorrent) + }) +} + +function seed (files) { + if (files.length === 0) return + util.log('Seeding ' + files.length + ' files') + + // Seed from WebTorrent + getClient(function (err, client) { + if (err) return util.error(err) + client.seed(files, onTorrent) + }) +} + +function onTorrent (torrent) { + upload.value = upload.defaultValue // reset upload element + + var torrentFileName = path.basename(torrent.name, path.extname(torrent.name)) + '.torrent' + + util.log( + 'Torrent info hash: ' + torrent.infoHash + ' ' + + '[Share link] ' + + '[Magnet URI] ' + + '[Download .torrent]' + ) + + function updateSpeed () { + var progress = (100 * torrent.progress).toFixed(1) + util.updateSpeed( + 'Peers: ' + torrent.swarm.wires.length + ' ' + + 'Progress: ' + progress + '% ' + + 'Download speed: ' + prettyBytes(window.client.downloadSpeed()) + '/s ' + + 'Upload speed: ' + prettyBytes(window.client.uploadSpeed()) + '/s' + ) + } + + torrent.swarm.on('download', updateSpeed) + torrent.swarm.on('upload', updateSpeed) + setInterval(updateSpeed, 5000) + updateSpeed() + + torrent.files.forEach(function (file) { + // append file + file.appendTo(util.logElem, function (err, elem) { + if (err) return util.error(err) + }) + + // append download link + file.getBlobURL(function (err, url) { + if (err) return util.error(err) + + var a = document.createElement('a') + a.target = '_blank' + a.download = file.name + a.href = url + a.textContent = 'Download ' + file.name + util.log(a) + }) + }) +} + +// function onBeforeUnload (e) { +// if (!e) e = window.event + +// if (!window.client || window.client.torrents.length === 0) return + +// var isLoneSeeder = window.client.torrents.some(function (torrent) { +// return torrent.swarm && torrent.swarm.numPeers === 0 && torrent.progress === 1 +// }) +// if (!isLoneSeeder) return + +// var names = listify(window.client.torrents.map(function (torrent) { +// return '"' + (torrent.name || torrent.infoHash) + '"' +// })) + +// var theseTorrents = window.client.torrents.length >= 2 +// ? 'these torrents' +// : 'this torrent' +// var message = 'You are the only person sharing ' + names + '. ' + +// 'Consider leaving this page open to continue sharing ' + theseTorrents + '.' + +// if (e) e.returnValue = message // IE, Firefox +// return message // Safari, Chrome +// } diff --git a/client/util.js b/client/util.js new file mode 100644 index 00000000..130710a9 --- /dev/null +++ b/client/util.js @@ -0,0 +1,38 @@ +var logElem = exports.logElem = document.querySelector('.log') +var speed = document.querySelector('.speed') +var logHeading = document.querySelector('#logHeading') + +exports.log = function log (item) { + logHeading.style.display = 'block' + if (typeof item === 'string') { + var p = document.createElement('p') + p.innerHTML = item + logElem.appendChild(p) + return p + } else { + logElem.appendChild(item) + exports.lineBreak() + return item + } +} + +exports.lineBreak = function lineBreak () { + logElem.appendChild(document.createElement('br')) +} + +// replace the last P in the log +exports.updateSpeed = function updateSpeed (str) { + speed.innerHTML = str +} + +exports.warning = function warning (err) { + console.error(err.stack || err.message || err) + exports.log(err.message || err) +} + +exports.error = function error (err) { + console.error(err.stack || err.message || err) + var p = exports.log(err.message || err) + p.style.color = 'red' + p.style.fontWeight = 'bold' +} diff --git a/index.css b/index.css index 78bb8059..e2e3d8b8 100644 --- a/index.css +++ b/index.css @@ -1,27 +1,103 @@ +.clearfix:before, +.clearfix:after { + content: ''; + display: table +} +.clearfix:after { + clear: both +} +.drag-layer { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); +} +body.drag .drag-layer { + display: block +} +*, +*:after, +*:before { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box +} html, body { - padding: 0; - margin: 0; + margin: 0; + padding: 0; + height: 100%; + overflow: auto; + font-family: -apple-system, 'Helvetica Neue', Helvetica, sans-serif; } - body { - font-family: -apple-system, 'Helvetica Neue', Helvetica, sans-serif; + font-family: sans-serif; } - -header { - position: absolute; - width: 500px; - height: 250px; - top: 50%; - left: 50%; - margin-top: -125px; - margin-left: -250px; - text-align: center; +body.drag::before { + content: ''; + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: rgba(255, 0, 0, 0.3); + border: 5px #f00 dashed; } - -header h1 { - font-size: 60px; - font-weight: 100; - margin: 0; - padding: 0; +main { + margin: 20px; +} +label { + margin-right: 10px; +} +input, +button { + font-size: 16px; +} +input[name=torrentId] { + width: 400px; +} +.log, +.speed { + font-family: monospace; + font-size: 14px; +} +.speed:not(:empty) { + padding: 20px; + margin: 10px 0; + background: #eee; +} +.log p { + margin: 0; +} +footer { + font-size: .8em; + margin: 30px 0; +} +video, +img, +audio, +iframe { + display: block; +} +video, +img, +audio { + max-width: 100%; + margin: 10px 0; +} +video { + min-width: 480px; + min-height: 270px; +} +audio { + width: 100%; + height: 50px; +} +iframe { + width: 1000px; + max-width: 100%; + height: 600px; } diff --git a/index.html b/index.html index d5c6efbf..60d1a0e4 100644 --- a/index.html +++ b/index.html @@ -1,17 +1,33 @@ - + - Electron boilerplate - + WebTorrent + + -
+
-

Electron boilerplate

+

WebTorrent

-
-
+ +
+
+ +

Start seeding

+

+ Drag-and-drop a file (or files) to begin sharing. Or choose a file: + +

+ +

Start downloading

+
+ + + +
+ diff --git a/package.json b/package.json index 931ab324..f6358a6b 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,13 @@ "url": "https://github.com/feross/webtorrent-app/issues" }, "dependencies": { - "electron-debug": "^0.5.0" + "drag-drop": "^2.3.1", + "electron-debug": "^0.5.0", + "pretty-bytes": "^3.0.0", + "thunky": "^0.1.0", + "upload-element": "^1.0.1", + "webtorrent": "^0.68.0", + "xhr": "^2.2.0" }, "devDependencies": { "electron-packager": "^5.0.0",