diff --git a/background.html b/background.html new file mode 100644 index 00000000..080688ce --- /dev/null +++ b/background.html @@ -0,0 +1,11 @@ + + +
+ + + + + + diff --git a/client/capture-video-frame.js b/background/capture-video-frame.js similarity index 100% rename from client/capture-video-frame.js rename to background/capture-video-frame.js diff --git a/background/index.js b/background/index.js new file mode 100644 index 00000000..8370fd84 --- /dev/null +++ b/background/index.js @@ -0,0 +1,130 @@ +var createTorrent = require('create-torrent') +var electron = require('electron') +var path = require('path') +var prettyBytes = require('pretty-bytes') +var throttle = require('throttleit') +var thunky = require('thunky') +var torrentPoster = require('./torrent-poster') +var WebTorrent = require('webtorrent') +var xhr = require('xhr') + +var ipc = electron.ipcRenderer + +global.WEBTORRENT_ANNOUNCE = createTorrent.announceList + .map(function (arr) { + return arr[0] + }) + .filter(function (url) { + return url.indexOf('wss://') === 0 || url.indexOf('ws://') === 0 + }) + +var getClient = thunky(function (cb) { + getRtcConfig('https://instant.io/rtcConfig', function (err, rtcConfig) { + if (err) onError(err) + var client = window.client = new WebTorrent({ + rtcConfig: rtcConfig + }) + client.on('warning', onWarning) + client.on('error', onError) + cb(null, client) + }) +}) + +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) + ) + } + console.log('got rtc config: %o', rtcConfig) + cb(null, rtcConfig) + } + }) +} + +// For performance, create the client immediately +getClient(function () {}) + +ipc.on('action', function (event, action, ...args) { + console.log('action %s', action) + if (action === 'addTorrent') { + downloadTorrent(args[0]) + } else { + throw new Error('unrecognized action ' + action) + } +}) + +function downloadTorrent (torrentId) { + console.log('Downloading torrent from %s', torrentId) + getClient(function (err, client) { + if (err) return onError(err) + client.add(torrentId, onTorrent) + }) +} + +function downloadTorrentFile (file) { + console.log('Downloading torrent from ' + file.name + '') + getClient(function (err, client) { + if (err) return onError(err) + client.add(file, onTorrent) + }) +} + +function seed (files) { + if (files.length === 0) return + console.log('Seeding ' + files.length + ' files') + + // Seed from WebTorrent + getClient(function (err, client) { + if (err) return onError(err) + client.seed(files, onTorrent) + }) +} + +function onTorrent (torrent) { + console.log('done?', torrent.done) + var torrentFileName = path.basename(torrent.name, path.extname(torrent.name)) + '.torrent' + + console.log('"' + torrentFileName + '" contains ' + torrent.files.length + ' files:') + torrent.files.forEach(function (file) { + console.log(' - ' + file.name + ' (' + prettyBytes(file.length) + ')') + }) + + function updateSpeed () { + ipc.send('') + 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.on('download', throttle(updateSpeed, 250)) + torrent.on('upload', throttle(updateSpeed, 250)) + setInterval(updateSpeed, 5000) + updateSpeed() + + // torrentPoster(torrent, function (err, buf) { + // if (err) return onError(err) + // var img = document.createElement('img') + // img.src = URL.createObjectURL(new Blob([ buf ], { type: 'image/png' })) + // document.body.appendChild(img) + // }) +} + +function onError (err) { + console.error(err.stack) +} + +function onWarning (err) { + console.log('warning: ' + err.message) +} diff --git a/client/torrent-poster.js b/background/torrent-poster.js similarity index 100% rename from client/torrent-poster.js rename to background/torrent-poster.js diff --git a/client/index.js b/client/index.js index 12abe95f..0182b1c4 100644 --- a/client/index.js +++ b/client/index.js @@ -1,26 +1,75 @@ +var debug = require('debug/browser')('app:client') + var createElement = require('virtual-dom/create-element') var diff = require('virtual-dom/diff') +var dragDrop = require('drag-drop') +var electron = require('electron') var patch = require('virtual-dom/patch') +var ipc = electron.ipcRenderer + var App = require('./views/app') var state = { - count: 0 + torrents: [ + { name: 'Torrent 1' }, + { name: 'Torrent 2' } + ] } -// Init app -var currentVDom = App(state) +var currentVDom = App(state, handler) var rootElement = createElement(currentVDom) document.body.appendChild(rootElement) function update () { - var newVDom = App(state) + debug('update') + var newVDom = App(state, handler) var patches = diff(currentVDom, newVDom) rootElement = patch(rootElement, patches) currentVDom = newVDom } -setInterval(function () { - state.count += 1 - update() -}, 1000) +function handler (action, ...args) { + debug('handler: %s', action) + ipc.send('action', action, ...args) +} + +// Seed via drag-and-drop +dragDrop('body', onFiles) + +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) +} + +// Seed via upload input element +// var uploadElement = require('upload-element') +// var upload = document.querySelector('input[name=upload]') +// uploadElement(upload, function (err, files) { +// if (err) return onError(err) +// files = files.map(function (file) { return file.file }) +// onFiles(files) +// }) + +// Download via input element +// document.querySelector('form').addEventListener('submit', function (e) { +// e.preventDefault() +// downloadTorrent(document.querySelector('form input[name=torrentId]').value.trim()) +// }) diff --git a/client/util.js b/client/util.js deleted file mode 100644 index 130710a9..00000000 --- a/client/util.js +++ /dev/null @@ -1,38 +0,0 @@ -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/client/views/app.js b/client/views/app.js index 484987bc..c01e7600 100644 --- a/client/views/app.js +++ b/client/views/app.js @@ -2,15 +2,26 @@ module.exports = App var h = require('virtual-dom/h') -function App (state) { - var count = state.count - return h('div', { - style: { - textAlign: 'center', - lineHeight: (100 + count) + 'px', - border: '1px solid red', - width: (100 + count) + 'px', - height: (100 + count) + 'px' - } - }, [ String(count) ]) +function App (state, handler) { + var torrents = state.torrents + + var list = torrents.map(function (torrent) { + return h('.torrent', [ + h('.name', torrent.name) + ]) + }) + + return h('.app', [ + h('h1', 'WebTorrent'), + h('.torrent-list', list), + h('.add', [ + h('button', { + onclick: onAddTorrent + }, 'Add New Torrent') + ]) + ]) + + function onAddTorrent (e) { + handler('addTorrent', '6a9759bffd5c0af65319979fb7832189f4f3c35d') + } } diff --git a/client/webtorrent.js b/client/webtorrent.js deleted file mode 100644 index 5811b7a2..00000000 --- a/client/webtorrent.js +++ /dev/null @@ -1,194 +0,0 @@ -/* global URL, Blob */ - -var createTorrent = require('create-torrent') -var debug = require('debug')('instant.io') -var dragDrop = require('drag-drop') -var path = require('path') -var prettyBytes = require('pretty-bytes') -var throttle = require('throttleit') -var thunky = require('thunky') -var torrentPoster = require('./torrent-poster') -var uploadElement = require('upload-element') -var WebTorrent = require('webtorrent') -var xhr = require('xhr') - -var util = require('./util') - -global.WEBTORRENT_ANNOUNCE = createTorrent.announceList - .map(function (arr) { - return arr[0] - }) - .filter(function (url) { - return url.indexOf('wss://') === 0 || url.indexOf('ws://') === 0 - }) - -var getClient = thunky(function (cb) { - getRtcConfig('https://instant.io/rtcConfig', function (err, rtcConfig) { - if (err) util.error(err) - 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.trim()) -}) - -// Download by URL hash -onHashChange() -window.addEventListener('hashchange', onHashChange) -function onHashChange () { - var hash = decodeURIComponent(window.location.hash.substring(1)).trim() - if (hash !== '') downloadTorrent(hash) -} - -// 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('"' + torrentFileName + '" contains ' + torrent.files.length + ' files:') - torrent.files.forEach(function (file) { - util.log(' - ' + file.name + ' (' + prettyBytes(file.length) + ')') - }) - - 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.on('download', throttle(updateSpeed, 250)) - torrent.on('upload', throttle(updateSpeed, 250)) - setInterval(updateSpeed, 5000) - updateSpeed() - - torrentPoster(torrent, function (err, buf) { - if (err) return util.error(err) - var img = document.createElement('img') - img.src = URL.createObjectURL(new Blob([ buf ], { type: 'image/png' })) - document.body.appendChild(img) - }) - - // TODO: play torrent when user clicks button - // 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) - // }) - // }) -} diff --git a/index.js b/index.js index 5a0cf382..3645618b 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,11 @@ +require('debug/browser') + +var debug = require('debug')('index') var electron = require('electron') var path = require('path') var app = electron.app +var ipc = electron.ipcMain // report crashes // require('crash-reporter').start({ @@ -14,11 +18,12 @@ var app = electron.app // adds debug features like hotkeys for triggering dev tools and reload require('electron-debug')() -// prevent window being garbage collected -var mainWindow +// prevent windows from being garbage collected +var mainWindow, backgroundWindow // eslint-disable-line no-unused-vars app.on('ready', function () { mainWindow = createMainWindow() + backgroundWindow = createBackgroundWindow() }) app.on('activate', function () { @@ -29,15 +34,43 @@ app.on('window-all-closed', function () { if (process.platform !== 'darwin') app.quit() }) +ipc.on('action', function (event, action, ...args) { + debug('action %s', action) + backgroundWindow.webContents.send('action', action, ...args) +}) + function createMainWindow () { - const win = new electron.BrowserWindow({ + var win = new electron.BrowserWindow({ width: 600, height: 400, - titleBarStyle: 'hidden' + title: 'WebTorrent', + // titleBarStyle: 'hidden', + show: false + }) + win.loadURL('file://' + path.join(__dirname, 'main.html')) + win.webContents.on('did-finish-load', function () { + win.show() }) - win.loadURL('file://' + path.join(__dirname, 'index.html')) win.once('closed', function () { mainWindow = null }) return win } + +function createBackgroundWindow () { + var opts = debug.enabled + ? { width: 600, height: 400, x: 0, y: 0 } + : { width: 0, height: 0, show: false } + var win = new electron.BrowserWindow(opts) + win.loadURL('file://' + path.join(__dirname, 'background.html')) + win.once('closed', function () { + backgroundWindow = null + }) + return win +} + +// var progress = 0 +// setInterval(function () { +// progress += 0.1 +// mainWindow.setProgressBar(progress) +// }, 1000) diff --git a/index.html b/main.html similarity index 100% rename from index.html rename to main.html diff --git a/package.json b/package.json index 1af724d0..39fc28fe 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "devDependencies": { "electron-packager": "^5.0.0", "electron-prebuilt": "^0.36.0", - "pre-commit": "^1.1.2", "standard": "^6.0.5" }, "electronVersion": "0.36.0", @@ -49,6 +48,7 @@ }, "scripts": { "build": "electron-packager . $npm_package_productName --out=dist --ignore='^/dist$' --prune --asar --all --version=$npm_package_electronVersion", + "debug": "DEBUG=* electron .", "start": "electron .", "test": "standard" }