From 866019a925195cdbe35636a151dcda27f7d2c2b8 Mon Sep 17 00:00:00 2001 From: Feross Aboukhadijeh Date: Thu, 2 Mar 2017 14:01:07 -0800 Subject: [PATCH 01/13] Handle .torrent files in Torrent Viewer --- app/browser/webtorrent.js | 69 ++++++++++++++++++++++++++++++++++++++- app/filtering.js | 6 ++-- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/app/browser/webtorrent.js b/app/browser/webtorrent.js index f5a44945c9d..54fefd3c7bf 100644 --- a/app/browser/webtorrent.js +++ b/app/browser/webtorrent.js @@ -1,11 +1,20 @@ const electron = require('electron') const ipc = electron.ipcMain +const appUrlUtil = require('../../js/lib/appUrlUtil') const messages = require('../../js/constants/messages') +const Filtering = require('../filtering') // Set to see communication between WebTorrent and torrent viewer tabs const DEBUG_IPC = false if (DEBUG_IPC) console.log('WebTorrent IPC debugging enabled') +// var VIEWER_URL = chrome.extension.getURL('../extensions/torrent/webtorrent.html') +var VIEWER_URL = appUrlUtil.getTorrentExtUrl('webtorrent.html') + +function getViewerURL (torrentUrl) { + return VIEWER_URL + '#' + encodeURIComponent(torrentUrl) +} + // Connects to the BitTorrent network // Communicates with the WebTorrentRemoteClients via message passing let server = null @@ -23,7 +32,7 @@ function init (state, action) { channels[msg.clientKey] = e.sender server.receive(msg) }) - + setupFiltering() return state } @@ -43,6 +52,64 @@ function send (msg) { channel.send(messages.TORRENT_MESSAGE, msg) } +function setupFiltering () { + Filtering.registerHeadersReceivedFilteringCB(function (details, isPrivate) { + if (details.method !== 'GET') { + return {} + } + if (!isTorrentFile(details)) { + return {} + } + + var viewerUrl = getViewerURL(details.url) + + return { + responseHeaders: { + 'Location': [ viewerUrl ] + }, + statusLine: 'HTTP/1.1 301 Moved Permanently', + resourceName: 'webtorrent' + } + }) +} + +/** + * Check if the request is a torrent file. + * @param {Object} details First argument of the webRequest.onHeadersReceived + * event. The properties "responseHeaders" and "url" + * are read. + * @return {boolean} True if the resource is a torrent file. + */ +function isTorrentFile (details) { + var header = getHeader(details.responseHeaders, 'content-type') + if (header) { + var headerValue = header.toLowerCase().split(';', 1)[0].trim() + if (headerValue === 'application/x-bittorrent') { + return true + } + if (headerValue === 'application/octet-stream') { + if (details.url.toLowerCase().indexOf('.torrent') > 0) { + return true + } + var cdHeader = + getHeader(details.responseHeaders, 'content-disposition') + if (cdHeader && /\.torrent(["']|$)/i.test(cdHeader)) { + return true + } + } + } + return false +} + +function getHeader (headers, headerName) { + var headerNames = Object.keys(headers) + for (var i = 0; i < headerNames.length; ++i) { + if (headerNames[i].toLowerCase() === headerName) { + return headers[headerNames[i]][0] + } + } +} + module.exports = { init, resourceName: 'webtorrent' diff --git a/app/filtering.js b/app/filtering.js index c604c1667f4..1a05dc115d7 100644 --- a/app/filtering.js +++ b/app/filtering.js @@ -287,7 +287,6 @@ function registerForBeforeSendHeaders (session, partition) { */ function registerForHeadersReceived (session, partition) { const isPrivate = module.exports.isPrivate(partition) - // Note that onBeforeRedirect listener doesn't take a callback session.webRequest.onHeadersReceived(function (details, cb) { // Using an electron binary which isn't from Brave if (shouldIgnoreUrl(details)) { @@ -306,7 +305,10 @@ function registerForHeadersReceived (session, partition) { continue } if (results.responseHeaders) { - cb({responseHeaders: results.responseHeaders}) + cb({ + responseHeaders: results.responseHeaders, + statusLine: results.statusLine + }) return } } From 13475b52073f85b4d2c0b35210bd35ca004014d4 Mon Sep 17 00:00:00 2001 From: Feross Aboukhadijeh Date: Thu, 16 Mar 2017 14:39:10 -0700 Subject: [PATCH 02/13] break out torrent viewer into multiple components --- js/webtorrent/components/app.js | 34 +++++++ js/webtorrent/components/torrentFileList.js | 4 +- js/webtorrent/components/torrentViewer.js | 4 +- js/webtorrent/entry.js | 98 ++++++++------------- 4 files changed, 74 insertions(+), 66 deletions(-) create mode 100644 js/webtorrent/components/app.js diff --git a/js/webtorrent/components/app.js b/js/webtorrent/components/app.js new file mode 100644 index 00000000000..02d0bab2aee --- /dev/null +++ b/js/webtorrent/components/app.js @@ -0,0 +1,34 @@ +const React = require('react') + +const MediaViewer = require('./mediaViewer') +const TorrentViewer = require('./torrentViewer') + +class App extends React.Component { + render () { + const { + torrent, + torrentId, + errorMessage, + parsedTorrent, + dispatch + } = this.props.store + + const ix = parsedTorrent && parsedTorrent.ix // Selected file index + const name = parsedTorrent && parsedTorrent.name + + if (torrent && ix != null) { + return + } else { + return ( + + ) + } + } +} + +module.exports = App diff --git a/js/webtorrent/components/torrentFileList.js b/js/webtorrent/components/torrentFileList.js index c837139d442..859983807e3 100644 --- a/js/webtorrent/components/torrentFileList.js +++ b/js/webtorrent/components/torrentFileList.js @@ -40,7 +40,7 @@ class TorrentFileList extends React.Component { } renderFileLink (file, isDownload) { - const { torrent, torrentID } = this.props + const { torrent, torrentId } = this.props const ix = torrent.files.indexOf(file) if (isDownload) { if (torrent.serverURL) { @@ -50,7 +50,7 @@ class TorrentFileList extends React.Component { return
// No download links until the server is ready } } else { - const magnetURL = torrentID + '&ix=' + ix + const magnetURL = torrentId + '&ix=' + ix return {file.name} } } diff --git a/js/webtorrent/components/torrentViewer.js b/js/webtorrent/components/torrentViewer.js index 94b7544c469..307de7469cf 100644 --- a/js/webtorrent/components/torrentViewer.js +++ b/js/webtorrent/components/torrentViewer.js @@ -12,7 +12,7 @@ class TorrentViewer extends React.Component { } render () { - const {torrent, torrentID, name, errorMessage, dispatch} = this.props + const {torrent, torrentId, name, errorMessage, dispatch} = this.props let titleElem, mainButtonId if (torrent) { @@ -62,7 +62,7 @@ class TorrentViewer extends React.Component { + torrentId={torrentId} /> {legalNotice}
diff --git a/js/webtorrent/entry.js b/js/webtorrent/entry.js index 026dd7a9543..3b730e3d286 100644 --- a/js/webtorrent/entry.js +++ b/js/webtorrent/entry.js @@ -7,33 +7,32 @@ const React = require('react') const ReactDOM = require('react-dom') const WebTorrentRemoteClient = require('webtorrent-remote/client') -// React Components -const MediaViewer = require('./components/mediaViewer') -const TorrentViewer = require('./components/torrentViewer') +// React Component +const App = require('./components/app') // Stylesheets require('../../less/webtorrent.less') require('../../node_modules/font-awesome/css/font-awesome.css') -// UI state object. Pure function from `state` -> React element. -const state = { - torrentID: window.decodeURIComponent(window.location.hash.substring(1)), +// UI state object. Pure function from state -> React element. +const store = { + torrentId: window.decodeURIComponent(window.location.hash.substring(1)), parsedTorrent: null, client: null, torrent: null, errorMessage: null } -window.state = state /* for easier debugging */ +window.store = store /* for easier debugging */ -state.parsedTorrent = parseTorrent(state.torrentID) +store.parsedTorrent = parseTorrent(store.torrentId) // Create the client, set up IPC to the WebTorrentRemoteServer -state.client = new WebTorrentRemoteClient(send) -state.client.on('warning', onWarning) -state.client.on('error', onError) +store.client = new WebTorrentRemoteClient(send) +store.client.on('warning', onWarning) +store.client.on('error', onError) ipc.on(messages.TORRENT_MESSAGE, function (e, msg) { - state.client.receive(msg) + store.client.receive(msg) }) function send (msg) { @@ -42,27 +41,29 @@ function send (msg) { // Clean up the client before the window exits window.addEventListener('beforeunload', function () { - state.client.destroy({delay: 1000}) + store.client.destroy({delay: 1000}) }) // Check whether we're already part of this swarm. If not, show a Start button. -state.client.get(state.torrentID, function (err, torrent) { +store.client.get(store.torrentId, function (err, torrent) { if (!err) { - state.torrent = torrent + store.torrent = torrent addTorrentEvents(torrent) } - render() -}) -// Page starts blank, once you call render() it shows a continuously updating torrent UI -function render () { + // Page starts blank. This shows a continuously updating torrent UI update() setInterval(update, 1000) -} +}) function update () { - const elem = + const elem = ReactDOM.render(elem, document.querySelector('#appContainer')) + + // Update page title + if (store.parsedTorrent && store.parsedTorrent.name) { + document.title = store.parsedTorrent.name + } } function addTorrentEvents (torrent) { @@ -75,28 +76,28 @@ function onWarning (err) { } function onError (err) { - state.errorMessage = err.message + store.errorMessage = err.message } function start () { - state.client.add(state.torrentID, onAdded, {server: {}}) + store.client.add(store.torrentId, onAdded, {server: {}}) } function onAdded (err, torrent) { if (err) { - state.errorMessage = err.message + store.errorMessage = err.message return console.error(err) } - state.torrent = torrent + store.torrent = torrent addTorrentEvents(torrent) update() } function saveTorrentFile () { - let parsedTorrent = parseTorrent(state.torrentID) + let parsedTorrent = parseTorrent(store.torrentId) let torrentFile = parseTorrent.toTorrentFile(parsedTorrent) - let torrentFileName = state.parsedTorrent.name + '.torrent' + let torrentFileName = parsedTorrent.name + '.torrent' let torrentFileBlobURL = URL.createObjectURL( new Blob([torrentFile], { type: 'application/x-bittorrent' } @@ -109,40 +110,13 @@ function saveTorrentFile () { a.click() } -class App extends React.Component { - constructor () { - super() - this.dispatch = this.dispatch.bind(this) - } - - dispatch (action) { - switch (action) { - case 'start': - return start() - case 'saveTorrentFile': - return saveTorrentFile() - default: - console.error('Ignoring unknown dispatch type: ' + JSON.stringify(action)) - } - } - - render () { - const {torrent, torrentID, errorMessage, parsedTorrent} = state - const ix = parsedTorrent && parsedTorrent.ix // Selected file index - let name = parsedTorrent && parsedTorrent.name - if (name) document.title = name - - if (state.torrent && ix != null) { - return - } else { - return ( - - ) - } +function dispatch (action) { + switch (action) { + case 'start': + return start() + case 'saveTorrentFile': + return saveTorrentFile() + default: + console.error('Ignoring unknown dispatch type: ' + JSON.stringify(action)) } } From 9b5061d2bde3ae7885e2e2236c34323119875a28 Mon Sep 17 00:00:00 2001 From: Feross Aboukhadijeh Date: Thu, 16 Mar 2017 18:01:28 -0700 Subject: [PATCH 03/13] Refactor: prep for next version of webtorrent-remote --- js/webtorrent/components/app.js | 19 ++-- js/webtorrent/components/mediaViewer.js | 8 +- js/webtorrent/components/torrentFileList.js | 10 +-- js/webtorrent/components/torrentViewer.js | 14 ++- js/webtorrent/entry.js | 97 ++++++++++++--------- 5 files changed, 86 insertions(+), 62 deletions(-) diff --git a/js/webtorrent/components/app.js b/js/webtorrent/components/app.js index 02d0bab2aee..6fb3b721893 100644 --- a/js/webtorrent/components/app.js +++ b/js/webtorrent/components/app.js @@ -6,26 +6,25 @@ const TorrentViewer = require('./torrentViewer') class App extends React.Component { render () { const { - torrent, + ix, + name, torrentId, - errorMessage, - parsedTorrent, - dispatch + torrent, + serverUrl, + errorMessage } = this.props.store - const ix = parsedTorrent && parsedTorrent.ix // Selected file index - const name = parsedTorrent && parsedTorrent.name - if (torrent && ix != null) { - return + return } else { return ( + dispatch={this.props.dispatch} /> ) } } diff --git a/js/webtorrent/components/mediaViewer.js b/js/webtorrent/components/mediaViewer.js index 95588155e3d..3bc07a9432a 100644 --- a/js/webtorrent/components/mediaViewer.js +++ b/js/webtorrent/components/mediaViewer.js @@ -19,16 +19,16 @@ const SUPPORTED_AUDIO_EXTENSIONS = [ module.exports = class MediaViewer extends React.Component { render () { - const torrent = this.props.torrent - const ix = this.props.ix + const { torrent, serverUrl, ix } = this.props + const file = torrent.files[ix] const fileExt = file && getExtension(file.name) const isVideo = SUPPORTED_VIDEO_EXTENSIONS.includes(fileExt) const isAudio = SUPPORTED_AUDIO_EXTENSIONS.includes(fileExt) - const fileURL = torrent.serverURL && (torrent.serverURL + '/' + ix) + const fileURL = serverUrl && (serverUrl + '/' + ix) let content - if (torrent.serverURL == null) { + if (serverUrl == null) { content =
} else if (isVideo) { content =