diff --git a/.gitignore b/.gitignore index f36f431cd3d5..cfeb426ab62a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .tags* /.idea/ /browser/resources/brave_extension/ +/components/brave_webtorrent/extension/out/ /dist/ /out/ /vendor/requests @@ -39,4 +40,4 @@ npm-debug.log *~ CMakeLists.txt cmake-build-debug/ -coverage/ \ No newline at end of file +coverage/ diff --git a/brave_paks.gni b/brave_paks.gni index e61df6352083..1f38f84d1071 100644 --- a/brave_paks.gni +++ b/brave_paks.gni @@ -58,11 +58,13 @@ template("brave_extra_paks") { "$root_gen_dir/brave/brave_unscaled_resources.pak", "$root_gen_dir/components/brave_components_resources.pak", "$target_gen_dir/browser/resources/brave_extension.pak", + "$target_gen_dir/components/brave_webtorrent/brave_webtorrent_resources.pak", ] deps = [ "//brave/app:brave_generated_resources_grit", "//brave/app/theme:brave_unscaled_resources", + "//brave/components/brave_webtorrent:resources", "//brave/components/resources:brave_components_resources_grit", "//brave/browser/resources:brave_extension_grit", "//brave/common/extensions/api" diff --git a/browser/extensions/BUILD.gn b/browser/extensions/BUILD.gn index ac72d42fdaf2..ccb4f97a984a 100644 --- a/browser/extensions/BUILD.gn +++ b/browser/extensions/BUILD.gn @@ -24,6 +24,7 @@ source_set("extensions") { deps = [ "//brave/browser/resources:brave_extension_grit", + "//brave/components/brave_webtorrent:resources", "//chrome/browser", "//content/public/browser", "//extensions/browser", diff --git a/browser/extensions/brave_component_extension_resource_manager.cc b/browser/extensions/brave_component_extension_resource_manager.cc index 31feda2fdd18..e5db061e6d0c 100644 --- a/browser/extensions/brave_component_extension_resource_manager.cc +++ b/browser/extensions/brave_component_extension_resource_manager.cc @@ -5,6 +5,7 @@ #include "brave/browser/extensions/brave_component_extension_resource_manager.h" #include "brave/browser/resources/grit/brave_extension_resources_map.h" +#include "brave/components/brave_webtorrent/grit/brave_webtorrent_resources_map.h" namespace extensions { @@ -13,6 +14,10 @@ BraveComponentExtensionResourceManager() { AddComponentResourceEntries( kBraveExtension, kBraveExtensionSize); + + AddComponentResourceEntries( + kBraveWebtorrentResources, + kBraveWebtorrentResourcesSize); } BraveComponentExtensionResourceManager:: diff --git a/browser/extensions/brave_component_loader.cc b/browser/extensions/brave_component_loader.cc index b1966688c122..e2af51c77783 100644 --- a/browser/extensions/brave_component_loader.cc +++ b/browser/extensions/brave_component_loader.cc @@ -6,6 +6,7 @@ #include "base/command_line.h" #include "brave/common/brave_switches.h" +#include "brave/components/brave_webtorrent/grit/brave_webtorrent_resources.h" #include "components/grit/brave_components_resources.h" namespace extensions { @@ -33,6 +34,11 @@ void BraveComponentLoader::AddDefaultComponentExtensions( brave_extension_path.Append(FILE_PATH_LITERAL("brave_extension")); Add(IDR_BRAVE_EXTENSON, brave_extension_path); } + + base::FilePath brave_webtorrent_path(FILE_PATH_LITERAL("")); + brave_webtorrent_path = + brave_webtorrent_path.Append(FILE_PATH_LITERAL("brave_webtorrent")); + Add(IDR_BRAVE_WEBTORRENT, brave_webtorrent_path); } } // namespace extensions diff --git a/browser/extensions/brave_extension_provider.cc b/browser/extensions/brave_extension_provider.cc index 4426f00f0e6e..561140721eff 100644 --- a/browser/extensions/brave_extension_provider.cc +++ b/browser/extensions/brave_extension_provider.cc @@ -22,6 +22,7 @@ bool IsWhitelisted(const extensions::Extension* extension) { } static std::vector whitelist({ brave_extension_id, + brave_webtorrent_extension_id, pdfjs_extension_id, // 1Password "aomjjhallfgjeglblehebfpbcfeobpgk", @@ -126,7 +127,8 @@ bool BraveExtensionProvider::UserMayLoad(const Extension* extension, bool BraveExtensionProvider::MustRemainInstalled(const Extension* extension, base::string16* error) const { - return extension->id() == brave_extension_id; + return extension->id() == brave_extension_id || + extension->id() == brave_webtorrent_extension_id; } } // namespace extensions diff --git a/browser/resources/resource_ids b/browser/resources/resource_ids index 66a5407f764b..800eab522142 100644 --- a/browser/resources/resource_ids +++ b/browser/resources/resource_ids @@ -21,4 +21,5 @@ "brave/app/theme/brave_unscaled_resources.grd": { "includes": [36500], }, + # brave webtorrent 37500 } diff --git a/chromium_src/chrome/browser/extensions/component_extensions_whitelist/whitelist.cc b/chromium_src/chrome/browser/extensions/component_extensions_whitelist/whitelist.cc new file mode 100644 index 000000000000..463bc4f46e40 --- /dev/null +++ b/chromium_src/chrome/browser/extensions/component_extensions_whitelist/whitelist.cc @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#define IsComponentExtensionWhitelisted IsComponentExtensionWhitelisted_ChromiumImpl +#include "../../../../../../chrome/browser/extensions/component_extensions_whitelist/whitelist.cc" +#undef IsComponentExtensionWhitelisted + +#include "brave/common/extensions/extension_constants.h" +#include "components/grit/brave_components_resources.h" +#include "brave/components/brave_webtorrent/grit/brave_webtorrent_resources.h" + +namespace extensions { + + bool IsComponentExtensionWhitelisted(const std::string& extension_id) { + const char* const kAllowed[] = { + brave_extension_id, + brave_webtorrent_extension_id + }; + + for (size_t i = 0; i < arraysize(kAllowed); ++i) { + if (extension_id == kAllowed[i]) + return true; + } + + return IsComponentExtensionWhitelisted_ChromiumImpl(extension_id); + } + + bool IsComponentExtensionWhitelisted(int manifest_resource_id) { + switch (manifest_resource_id) { + // Please keep the list in alphabetical order. + case IDR_BRAVE_EXTENSON: + case IDR_BRAVE_WEBTORRENT: + return true; + } + + return IsComponentExtensionWhitelisted_ChromiumImpl(manifest_resource_id); + } + +} // namespace extensions + diff --git a/chromium_src/net/url_request/url_request_context_builder.cc b/chromium_src/net/url_request/url_request_context_builder.cc new file mode 100644 index 000000000000..e4291591b04e --- /dev/null +++ b/chromium_src/net/url_request/url_request_context_builder.cc @@ -0,0 +1,6 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "brave/net/url_request/magnet_protocol_handler.h" +#include "../../../../net/url_request/url_request_context_builder.cc" diff --git a/common/extensions/api/BUILD.gn b/common/extensions/api/BUILD.gn index 312d2fda535d..d32f5769b4e5 100644 --- a/common/extensions/api/BUILD.gn +++ b/common/extensions/api/BUILD.gn @@ -26,6 +26,7 @@ json_features("manifest_features") { sources = [ "//chrome/common/extensions/api/_manifest_features.json", "//extensions/common/api/_manifest_features.json", + "_manifest_features.json", ] } diff --git a/common/extensions/api/_api_features.json b/common/extensions/api/_api_features.json index 124160009744..0c4f663ed00a 100644 --- a/common/extensions/api/_api_features.json +++ b/common/extensions/api/_api_features.json @@ -48,5 +48,20 @@ "matches": [ "chrome://newtab/*" ] - }] + }], + "sockets.tcp": { + "dependencies": ["manifest:sockets"], + "contexts": ["blessed_extension"], + "whitelist": ["3D9518A72EB02667A773B69DBA9E72E0F4A37423"] + }, + "sockets.tcpServer": { + "dependencies": ["manifest:sockets"], + "contexts": ["blessed_extension"], + "whitelist": ["3D9518A72EB02667A773B69DBA9E72E0F4A37423"] + }, + "sockets.udp": { + "dependencies": ["manifest:sockets"], + "contexts": ["blessed_extension"], + "whitelist": ["3D9518A72EB02667A773B69DBA9E72E0F4A37423"] + } } diff --git a/common/extensions/api/_manifest_features.json b/common/extensions/api/_manifest_features.json new file mode 100644 index 000000000000..fcacdb730e04 --- /dev/null +++ b/common/extensions/api/_manifest_features.json @@ -0,0 +1,11 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +{ + "sockets": { + "channel": "stable", + "extension_types": ["extension", "platform_app"], + "whitelist": ["3D9518A72EB02667A773B69DBA9E72E0F4A37423"] + } +} diff --git a/common/extensions/extension_constants.cc b/common/extensions/extension_constants.cc index 33e9ea11b9db..ce9458ed3e70 100644 --- a/common/extensions/extension_constants.cc +++ b/common/extensions/extension_constants.cc @@ -5,5 +5,6 @@ #include "brave/common/extensions/extension_constants.h" const char brave_extension_id[] = "mnojpmjdmbbfmejpflffifhffcmidifd"; +const char brave_webtorrent_extension_id[] = "lgjmpdmojkpocjcopdikifhejkkjglho"; const char pdfjs_extension_id[] = "oemmndcbldboiebfnladdacbdfmadadm"; const char widevine_extension_id[] = "oimompecagnajdejgnnjijobebaeigek"; diff --git a/common/extensions/extension_constants.h b/common/extensions/extension_constants.h index 672959180d94..613d1c55361a 100644 --- a/common/extensions/extension_constants.h +++ b/common/extensions/extension_constants.h @@ -3,5 +3,6 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ extern const char brave_extension_id[]; +extern const char brave_webtorrent_extension_id[]; extern const char pdfjs_extension_id[]; extern const char widevine_extension_id[]; diff --git a/components/brave_adblock_ui/BUILD.gn b/components/brave_adblock_ui/BUILD.gn index c07456599c83..473a5f1eb58e 100644 --- a/components/brave_adblock_ui/BUILD.gn +++ b/components/brave_adblock_ui/BUILD.gn @@ -14,5 +14,5 @@ transpile_web_ui("brave_adblock_ui") { "reducers/adblock_reducer.ts", ] - bundle_name = "brave_adblock.bundle.js" + bundle_names = ["brave_adblock.bundle.js"] } diff --git a/components/brave_new_tab_ui/BUILD.gn b/components/brave_new_tab_ui/BUILD.gn index 1a3a4777269f..2082bd1460c8 100644 --- a/components/brave_new_tab_ui/BUILD.gn +++ b/components/brave_new_tab_ui/BUILD.gn @@ -18,5 +18,6 @@ transpile_web_ui("brave_new_tab_ui") { "reducers/index.ts", "reducers/new_tab_reducer.tsx", ] - bundle_name = "brave_new_tab.bundle.js" + + bundle_names = ["brave_new_tab.bundle.js"] } diff --git a/components/brave_rewards_ui/BUILD.gn b/components/brave_rewards_ui/BUILD.gn index 137fc8c2ea15..d31682c9adc0 100644 --- a/components/brave_rewards_ui/BUILD.gn +++ b/components/brave_rewards_ui/BUILD.gn @@ -13,5 +13,5 @@ transpile_web_ui("brave_rewards_ui") { "reducers/rewards_reducer.ts", ] - bundle_name = "brave_rewards.bundle.js" + bundle_names = ["brave_rewards.bundle.js"] } diff --git a/components/brave_webtorrent/BUILD.gn b/components/brave_webtorrent/BUILD.gn new file mode 100644 index 000000000000..c77c3e34b1f4 --- /dev/null +++ b/components/brave_webtorrent/BUILD.gn @@ -0,0 +1,18 @@ +import("//tools/grit/grit_rule.gni") +import("//tools/grit/repack.gni") + +grit("resources") { + source = "resources.grd" + outputs = [ + "grit/brave_webtorrent_resources_map.cc", + "grit/brave_webtorrent_resources_map.h", + "grit/brave_webtorrent_resources.h", + "brave_webtorrent_resources.pak", + ] + + deps = [ + "//brave/components/brave_webtorrent/extension:brave_webtorrent" + ] + + resource_ids = "" +} diff --git a/components/brave_webtorrent/extension/BUILD.gn b/components/brave_webtorrent/extension/BUILD.gn new file mode 100644 index 000000000000..fa55d5032a94 --- /dev/null +++ b/components/brave_webtorrent/extension/BUILD.gn @@ -0,0 +1,43 @@ +import("//brave/components/common/typescript.gni") + +transpile_web_ui("brave_webtorrent") { + inputs = [ + "background.ts", + "brave_webtorrent.html", + "brave_webtorrent.tsx", + "actions/tab_actions.ts", + "actions/webtorrent_actions.ts", + "actions/window_actions.ts", + "background/store.ts", + "background/webtorrent.ts", + "background/actions/tabActions.ts", + "background/actions/webtorrentActions.ts", + "background/actions/windowActions.ts", + "background/events/tabsEvents.ts", + "background/events/torrentEvents.ts", + "background/events/webtorrentEvents.ts", + "background/events/windowsEvents.ts", + "background/reducers/index.ts", + "background/reducers/webtorrent_reducer.ts", + "components/app.tsx", + "components/mediaViewer.tsx", + "components/torrentFileList.tsx", + "components/torrentStatus.tsx", + "components/torrentViewer.tsx", + "components/torrentViewerFooter.tsx", + "components/torrentViewerHeader.tsx", + "constants/tab_types.ts", + "constants/theme.ts", + "constants/webtorrentState.ts", + "constants/webtorrent_types.ts", + "constants/window_types.ts" + ] + + bundle_names = [ + "brave_webtorrent.bundle.js", + "brave_webtorrent_background.bundle.js" + ] + + output_dir = + "$root_gen_dir/../../../brave/components/brave_webtorrent/extension/out" +} diff --git a/components/brave_webtorrent/extension/actions/tab_actions.ts b/components/brave_webtorrent/extension/actions/tab_actions.ts new file mode 100644 index 000000000000..d0e946fc2b1f --- /dev/null +++ b/components/brave_webtorrent/extension/actions/tab_actions.ts @@ -0,0 +1,25 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { action } from 'typesafe-actions' + +// Constants +import { types } from '../constants/tab_types' + +export const tabCreated = (tab: chrome.tabs.Tab) => action(types.TAB_CREATED, { + tab +}) + +export const tabUpdated = (tabId: number, changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab) => + action(types.TAB_UPDATED, { + tabId, changeInfo, tab + }) + +export const tabRemoved = (tabId: number) => action(types.TAB_REMOVED, { + tabId +}) + +export const activeTabChanged = (tabId: number, windowId: number) => action(types.ACTIVE_TAB_CHANGED, { + tabId, windowId +}) diff --git a/components/brave_webtorrent/extension/actions/webtorrent_actions.ts b/components/brave_webtorrent/extension/actions/webtorrent_actions.ts new file mode 100644 index 000000000000..d2f558515c45 --- /dev/null +++ b/components/brave_webtorrent/extension/actions/webtorrent_actions.ts @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { action } from 'typesafe-actions' +import { Torrent } from 'webtorrent' + +// Constants +import { types } from '../constants/webtorrent_types' + +export const progressUpdated = (torrent: Torrent) => action(types.WEBTORRENT_PROGRESS_UPDATED, { torrent }) +export const infoUpdated = (torrent: Torrent) => action(types.WEBTORRENT_INFO_UPDATED, { torrent }) +export const serverUpdated = (torrent: Torrent, serverURL: string) => + action(types.WEBTORRENT_SERVER_UPDATED, { torrent, serverURL }) +export const startTorrent = (torrentId: string, tabId: number) => action(types.WEBTORRENT_START_TORRENT, { torrentId, tabId }) +export const stopDownload = (tabId: number) => action(types.WEBTORRENT_STOP_DOWNLOAD, { tabId }) diff --git a/components/brave_webtorrent/extension/actions/window_actions.ts b/components/brave_webtorrent/extension/actions/window_actions.ts new file mode 100644 index 000000000000..b54a4fed7326 --- /dev/null +++ b/components/brave_webtorrent/extension/actions/window_actions.ts @@ -0,0 +1,20 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { action } from 'typesafe-actions' + +// Constants +import { types } from '../constants/window_types' + +export const windowCreated = (window: chrome.windows.Window) => action(types.WINDOW_CREATED, { + window +}) + +export const windowRemoved = (windowId: number) => action(types.WINDOW_REMOVED, { + windowId +}) + +export const windowFocusChanged = (windowId: number) => action(types.WINDOW_FOCUS_CHANGED, { + windowId +}) diff --git a/components/brave_webtorrent/extension/background.ts b/components/brave_webtorrent/extension/background.ts new file mode 100644 index 000000000000..3d4da61e3ef1 --- /dev/null +++ b/components/brave_webtorrent/extension/background.ts @@ -0,0 +1,10 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { init } from './background/webtorrent' +init() + +require('./background/store') +require('./background/events/tabsEvents') +require('./background/events/windowsEvents') diff --git a/components/brave_webtorrent/extension/background/actions/tabActions.ts b/components/brave_webtorrent/extension/background/actions/tabActions.ts new file mode 100644 index 000000000000..751bf84a80f2 --- /dev/null +++ b/components/brave_webtorrent/extension/background/actions/tabActions.ts @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { bindActionCreators } from 'redux' +import store from '../store' +import * as tabActions from '../../actions/tab_actions' +export default bindActionCreators(tabActions, store.dispatch) diff --git a/components/brave_webtorrent/extension/background/actions/webtorrentActions.ts b/components/brave_webtorrent/extension/background/actions/webtorrentActions.ts new file mode 100644 index 000000000000..b7eef7ab074d --- /dev/null +++ b/components/brave_webtorrent/extension/background/actions/webtorrentActions.ts @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { bindActionCreators } from 'redux' +import store from '../store' +import * as webtorrentActions from '../../actions/webtorrent_actions' +export default bindActionCreators(webtorrentActions, store.dispatch) diff --git a/components/brave_webtorrent/extension/background/actions/windowActions.ts b/components/brave_webtorrent/extension/background/actions/windowActions.ts new file mode 100644 index 000000000000..948d7c928798 --- /dev/null +++ b/components/brave_webtorrent/extension/background/actions/windowActions.ts @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { bindActionCreators } from 'redux' +import store from '../store' +import * as windowActions from '../../actions/window_actions' +export default bindActionCreators(windowActions, store.dispatch) diff --git a/components/brave_webtorrent/extension/background/events/tabsEvents.ts b/components/brave_webtorrent/extension/background/events/tabsEvents.ts new file mode 100644 index 000000000000..e6c07b834112 --- /dev/null +++ b/components/brave_webtorrent/extension/background/events/tabsEvents.ts @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import tabActions from '../actions/tabActions' + +chrome.tabs.onCreated.addListener((tab: chrome.tabs.Tab) => { + tabActions.tabCreated(tab) +}) + +chrome.tabs.onUpdated.addListener((tabId: number, changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab) => { + tabActions.tabUpdated(tabId, changeInfo, tab) +}) + +chrome.tabs.onRemoved.addListener((tabId: number, removeInfo: chrome.tabs.TabRemoveInfo) => { + tabActions.tabRemoved(tabId) +}) + +chrome.tabs.onActivated.addListener((activeInfo: chrome.tabs.TabActiveInfo) => { + tabActions.activeTabChanged(activeInfo.tabId, activeInfo.windowId) +}) diff --git a/components/brave_webtorrent/extension/background/events/torrentEvents.ts b/components/brave_webtorrent/extension/background/events/torrentEvents.ts new file mode 100644 index 000000000000..99f955310d6f --- /dev/null +++ b/components/brave_webtorrent/extension/background/events/torrentEvents.ts @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { Torrent } from 'webtorrent' +import * as throttle from 'throttleit' + +import webtorrentActions from '../actions/webtorrentActions' +import { createServer } from '../webtorrent' + +export const addTorrentEvents = (torrent: Torrent) => { + torrent.on('done', () => { + webtorrentActions.progressUpdated(torrent) + }) + torrent.on('infoHash', () => { + console.log('infoHash event') + webtorrentActions.infoUpdated(torrent) + }) + torrent.on('metadata', () => { + console.log('metadata event') + webtorrentActions.infoUpdated(torrent) + }) + torrent.on('download', throttle((bytes: number) => { + webtorrentActions.progressUpdated(torrent) + }, 1000)) + torrent.on('upload', throttle((bytes: number) => { + webtorrentActions.progressUpdated(torrent) + }, 1000)) + torrent.on('ready', () => { + console.log('ready', torrent) + createServer(torrent, (serverURL: string) => { + webtorrentActions.serverUpdated(torrent, serverURL) + }) + }) + torrent.on('warning', (e: Error | string) => { + console.log('warning: ', torrent, e) + }) + torrent.on('error', (e: Error | string) => { + console.log('error: ', torrent, e) + }) +} diff --git a/components/brave_webtorrent/extension/background/events/webtorrentEvents.ts b/components/brave_webtorrent/extension/background/events/webtorrentEvents.ts new file mode 100644 index 000000000000..3c6f7e774144 --- /dev/null +++ b/components/brave_webtorrent/extension/background/events/webtorrentEvents.ts @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { Instance } from 'webtorrent' + +export const addWebtorrentEvents = (webtorrent: Instance) => { + webtorrent.on('error', (e: Error | string) => { + console.log('WebTorrent error: ', e) + }) +} diff --git a/components/brave_webtorrent/extension/background/events/windowsEvents.ts b/components/brave_webtorrent/extension/background/events/windowsEvents.ts new file mode 100644 index 000000000000..9a8a328f390c --- /dev/null +++ b/components/brave_webtorrent/extension/background/events/windowsEvents.ts @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import windowActions from '../actions/windowActions' + +chrome.windows.onFocusChanged.addListener((windowId: number) => { + windowActions.windowFocusChanged(windowId) +}) + +chrome.windows.onCreated.addListener((window: chrome.windows.Window) => { + windowActions.windowCreated(window) +}) + +chrome.windows.onRemoved.addListener((windowId: number) => { + windowActions.windowRemoved(windowId) +}) diff --git a/components/brave_webtorrent/extension/background/reducers/index.ts b/components/brave_webtorrent/extension/background/reducers/index.ts new file mode 100644 index 000000000000..2a5fb58e099b --- /dev/null +++ b/components/brave_webtorrent/extension/background/reducers/index.ts @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { combineReducers } from 'redux' + +// Utils +import webtorrentReducer from './webtorrent_reducer' +import { ApplicationState } from '../../constants/webtorrentState' + +export default combineReducers({ + torrentsData: webtorrentReducer +}) diff --git a/components/brave_webtorrent/extension/background/reducers/webtorrent_reducer.ts b/components/brave_webtorrent/extension/background/reducers/webtorrent_reducer.ts new file mode 100644 index 000000000000..29bc23161029 --- /dev/null +++ b/components/brave_webtorrent/extension/background/reducers/webtorrent_reducer.ts @@ -0,0 +1,222 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import * as ParseTorrent from 'parse-torrent' +import { Torrent } from 'webtorrent' + +// Constants +import * as tabTypes from '../../constants/tab_types' +import * as windowTypes from '../../constants/window_types' +import * as torrentTypes from '../../constants/webtorrent_types' +import { File, TorrentState, TorrentsState } from '../../constants/webtorrentState' + +// Utils +import { addTorrent, delTorrent, findTorrent } from '../webtorrent' + +const isTorrentPage = (url: URL) => { + const fullpath = url.origin + url.pathname + return fullpath === chrome.runtime.getURL('extension/brave_webtorrent.html') +} + +const focusedWindowChanged = (windowId: number, state: TorrentsState) => { + return { ...state, currentWindowId: windowId } +} + +const windowRemoved = (windowId: number, state: TorrentsState) => { + const { activeTabIds } = state + delete activeTabIds[windowId] + return { ...state, activeTabIds } +} + +const activeTabChanged = (tabId: number, windowId: number, state: TorrentsState) => { + const { activeTabIds } = state + activeTabIds[windowId] = tabId + return { ...state, activeTabIds } +} + +const tabUpdated = (tabId: number, url: string, state: TorrentsState) => { + const { torrentStateMap, torrentObjMap } = state + const origTorrentState: TorrentState = torrentStateMap[tabId] + const origInfoHash = origTorrentState ? origTorrentState.infoHash : undefined + let newTorrentState + let newInfoHash + + // delete old torrent state + delete torrentStateMap[tabId] // delete old torrent state + + // create new torrent state + const parsedURL = new window.URL(url) + if (isTorrentPage(parsedURL)) { // parse torrent + const torrentId = decodeURIComponent(parsedURL.search.substring(1)) + try { + const { name, infoHash, ix } = ParseTorrent(torrentId) + newInfoHash = infoHash + newTorrentState = { tabId, torrentId, name, infoHash, ix } + } catch (error) { + newTorrentState = { tabId, torrentId, errorMsg: error.message } + } + } + + // unsubscribe old torrent if not the same + const isSameTorrent = newInfoHash && origInfoHash === newInfoHash + if (origInfoHash && torrentObjMap[origInfoHash] && !isSameTorrent) { + torrentObjMap[origInfoHash].tabClients.delete(tabId) + if (!torrentObjMap[origInfoHash].tabClients.size) { + delete torrentObjMap[origInfoHash] + delTorrent(origInfoHash) + } + } + + // save new torrent state and subscribe directly if torrent already existed + if (newTorrentState) { + torrentStateMap[tabId] = newTorrentState + if (newInfoHash && torrentObjMap[newInfoHash]) { + torrentObjMap[newInfoHash].tabClients.add(tabId) + } + } + + return { ...state, torrentStateMap, torrentObjMap } +} + +const tabRemoved = (tabId: number, state: TorrentsState) => { + const { torrentStateMap, torrentObjMap } = state + const torrentState = torrentStateMap[tabId] + if (torrentState) { + const infoHash = torrentState.infoHash + if (infoHash && torrentObjMap[infoHash]) { // unsubscribe + torrentObjMap[infoHash].tabClients.delete(tabId) + if (!torrentObjMap[infoHash].tabClients.size) { + delete torrentObjMap[infoHash] + delTorrent(infoHash) + } + } + delete torrentStateMap[tabId] + } + + return { ...state, torrentStateMap, torrentObjMap } +} + +const startTorrent = (torrentId: string, tabId: number, state: TorrentsState) => { + const { torrentStateMap, torrentObjMap } = state + const torrentState = torrentStateMap[tabId] + + if (torrentState && torrentState.infoHash && + !findTorrent(torrentState.infoHash)) { + addTorrent(torrentId) // objectMap will be updated when info event is emitted + } + + return { ...state, torrentObjMap } +} + +const stopDownload = (tabId: number, state: TorrentsState) => { + const { torrentStateMap, torrentObjMap } = state + const infoHash = torrentStateMap[tabId].infoHash + + if (infoHash && torrentObjMap[infoHash]) { + delete torrentObjMap[infoHash] + delTorrent(infoHash) + } + + return { ...state, torrentStateMap, torrentObjMap } +} + +const updateProgress = (state: TorrentsState, torrent: Torrent) => { + const { torrentObjMap } = state + // don't add a new entry since the download might be stopped already + if (!torrentObjMap[torrent.infoHash]) { + return state + } + + const { downloaded, uploaded, downloadSpeed, uploadSpeed, progress, ratio, + numPeers, timeRemaining } = torrent + torrentObjMap[torrent.infoHash] = { ...torrentObjMap[torrent.infoHash], + downloaded, uploaded, downloadSpeed, uploadSpeed, progress, ratio, + numPeers, timeRemaining } + + return { ...state, torrentObjMap } +} + +const updateInfo = (state: TorrentsState, torrent: Torrent) => { + const { torrentStateMap, torrentObjMap } = state + const { downloaded, uploaded, downloadSpeed, uploadSpeed, progress, ratio, + numPeers, timeRemaining, infoHash } = torrent + let length: number = 0 + const files: File[] = torrent.files.map((file) => { + length += file.length + return { name: file.name, length: file.length } + }) + + const tabClients: Set = new Set() + Object.keys(torrentStateMap).filter( + key => torrentStateMap[key].infoHash === infoHash).map( + key => { + tabClients.add(torrentStateMap[key].tabId) + } + ) + + torrentObjMap[torrent.infoHash] = { ...torrentObjMap[torrent.infoHash], + files, downloaded, uploaded, downloadSpeed, uploadSpeed, progress, ratio, + numPeers, timeRemaining, length, tabClients } + + return { ...state, torrentStateMap, torrentObjMap } +} + +const updateServer = (state: TorrentsState, torrent: Torrent, serverURL: string) => { + const { torrentObjMap } = state + torrentObjMap[torrent.infoHash] = { ...torrentObjMap[torrent.infoHash], serverURL } + return { ...state, torrentObjMap } +} + +const defaultState: TorrentsState = { currentWindowId: -1, activeTabIds: {}, torrentStateMap: {}, torrentObjMap: {} } +const webtorrentReducer = (state: TorrentsState = defaultState, action: any) => { // TODO: modify any to be actual action type + const payload = action.payload + switch (action.type) { + case windowTypes.types.WINDOW_CREATED: + if (payload.window.focused || Object.keys(state.activeTabIds).length === 0) { + state = focusedWindowChanged(payload.window.id, state) + } + break + case windowTypes.types.WINDOW_REMOVED: + state = windowRemoved(payload.windowId, state) + break + case windowTypes.types.WINDOW_FOCUS_CHANGED: + state = focusedWindowChanged(payload.windowId, state) + break + case tabTypes.types.ACTIVE_TAB_CHANGED: + state = activeTabChanged(payload.tabId, payload.windowId, state) + break + case tabTypes.types.TAB_CREATED: + if (payload.tab.id && payload.tab.url) { + state = tabUpdated(payload.tab, payload.tab.url, state) + } + break + case tabTypes.types.TAB_UPDATED: + if (payload.changeInfo.url) { + state = tabUpdated(payload.tab.id, payload.changeInfo.url, state) + } + break + case tabTypes.types.TAB_REMOVED: + state = tabRemoved(payload.tabId, state) + break + case torrentTypes.types.WEBTORRENT_PROGRESS_UPDATED: + state = updateProgress(state, payload.torrent) + break + case torrentTypes.types.WEBTORRENT_INFO_UPDATED: + state = updateInfo(state, payload.torrent) + break + case torrentTypes.types.WEBTORRENT_SERVER_UPDATED: + state = updateServer(state, payload.torrent, payload.serverURL) + break + case torrentTypes.types.WEBTORRENT_START_TORRENT: + state = startTorrent(payload.torrentId, payload.tabId, state) + break + case torrentTypes.types.WEBTORRENT_STOP_DOWNLOAD: + state = stopDownload(payload.tabId, state) + break + } + + return state +} + +export default webtorrentReducer diff --git a/components/brave_webtorrent/extension/background/store.ts b/components/brave_webtorrent/extension/background/store.ts new file mode 100644 index 000000000000..3d0a0676b9b3 --- /dev/null +++ b/components/brave_webtorrent/extension/background/store.ts @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { applyMiddleware, createStore, Middleware } from 'redux' +import { wrapStore } from 'react-chrome-redux' +import { createLogger } from 'redux-logger' + +import reducers from './reducers' + +const logger = createLogger({ + collapsed: true +}) + +const getMiddleware = () => { + const args: Middleware[] = [] + if (process.env.NODE_ENV === `development`) { + args.push(logger) + } + + return applyMiddleware(...args) +} + +const initialState = {} +const store = createStore(reducers, initialState, getMiddleware()) + +wrapStore(store, { + portName: 'WEBTORRENT' +}) + +export default store diff --git a/components/brave_webtorrent/extension/background/webtorrent.ts b/components/brave_webtorrent/extension/background/webtorrent.ts new file mode 100644 index 000000000000..855d9c552f39 --- /dev/null +++ b/components/brave_webtorrent/extension/background/webtorrent.ts @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import * as WebTorrent from 'webtorrent' +import { addTorrentEvents } from './events/torrentEvents' +import { addWebtorrentEvents } from './events/webtorrentEvents' +import { AddressInfo } from 'net' + +let webTorrent: WebTorrent.Instance +let servers: { [key: string]: any } = { } + +export const init = () => { + webTorrent = new WebTorrent({ tracker: { wrtc: false } }) + addWebtorrentEvents(webTorrent) +} + +export const getWebTorrent = () => webTorrent + +export const createServer = (torrent: WebTorrent.Torrent, cb: (serverURL: string) => void) => { + if (!torrent.infoHash) return // torrent is not ready + + const server = torrent.createServer() + if (!server) return + + try { + server.listen(0, '127.0.0.1', undefined, () => { + // Explicitly cast server.address() to AddressInfo to access its + // properties. It's safe to cast here because the only possible type of + // server.address() here is AddressInfo since pipe name (as string) is + // not supported in chrome-net. + const addrInfo = server.address() as AddressInfo + servers[torrent.infoHash] = server + cb('http://' + addrInfo.address + ':' + addrInfo.port.toString()) + }) + } catch (error) { + console.log('server listen error: ', error) + } +} + +export const addTorrent = (torrentId: string) => { + const torrentObj = webTorrent.add(torrentId) + addTorrentEvents(torrentObj) +} + +export const findTorrent = (infoHash: string) => { + return webTorrent.torrents.find(torrent => torrent.infoHash === infoHash) +} + +export const delTorrent = (infoHash: string) => { + const torrent = findTorrent(infoHash) + if (torrent) torrent.destroy() + if (servers[infoHash]) { + servers[infoHash].close() + delete servers[infoHash] + } +} diff --git a/components/brave_webtorrent/extension/brave_webtorrent.html b/components/brave_webtorrent/extension/brave_webtorrent.html new file mode 100644 index 000000000000..25646c64c983 --- /dev/null +++ b/components/brave_webtorrent/extension/brave_webtorrent.html @@ -0,0 +1,14 @@ + + + + +Webtorrent + + + + +
+ + diff --git a/components/brave_webtorrent/extension/brave_webtorrent.tsx b/components/brave_webtorrent/extension/brave_webtorrent.tsx new file mode 100644 index 000000000000..1d98b2ba66c3 --- /dev/null +++ b/components/brave_webtorrent/extension/brave_webtorrent.tsx @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import * as React from 'react' +import { render } from 'react-dom' +import { Provider } from 'react-redux' +import { Store } from 'react-chrome-redux' + +// Components +import App from './components/app' + +// Constants +import { TorrentsState } from './constants/webtorrentState' + +const store: Store = new Store({ + portName: 'WEBTORRENT' +}) + +store.ready().then( + () => { + render( + + + , + document.getElementById('root')) + }) + .catch(() => { + console.error('Problem mounting webtorrent') + }) diff --git a/components/brave_webtorrent/extension/components/app.tsx b/components/brave_webtorrent/extension/components/app.tsx new file mode 100644 index 000000000000..16d36fd8553b --- /dev/null +++ b/components/brave_webtorrent/extension/components/app.tsx @@ -0,0 +1,72 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import * as React from 'react' +import { bindActionCreators, Dispatch } from 'redux' +import { connect } from 'react-redux' + +// Components +import MediaViewer from './mediaViewer' +import TorrentViewer from './torrentViewer' + +// Constants +import { TorrentObj, TorrentState, ApplicationState, getTorrentObj, getTorrentState } from '../constants/webtorrentState' + +// Utils +import * as torrentActions from '../actions/webtorrent_actions' + +// Assets +require('../../../styles/webtorrent.less') + +interface Props { + torrentState: TorrentState + torrentObj?: TorrentObj + actions: any +} + +export class BraveWebtorrentPage extends React.Component { + render () { + const { actions, torrentState, torrentObj } = this.props + const torrentId = decodeURIComponent(window.location.search.substring(1)) + + // The active tab change might not be propagated here yet, so we might get + // the old active tabId here which might be a different torrent page or a + // non-torrent page. + if (!torrentState || torrentId !== torrentState.torrentId) { + return (
Loading...
) + } + + if (torrentObj && typeof(torrentState.ix) === 'number') { + return ( + ) + } + + return ( + ) + } +} + +export const mapStateToProps = (state: ApplicationState) => { + return { torrentState: getTorrentState(state.torrentsData), + torrentObj: getTorrentObj(state.torrentsData) } +} + +export const mapDispatchToProps = (dispatch: Dispatch) => ({ + actions: bindActionCreators(torrentActions, dispatch) +}) + +export default connect( + mapStateToProps, + mapDispatchToProps +)(BraveWebtorrentPage) diff --git a/components/brave_webtorrent/extension/components/mediaViewer.tsx b/components/brave_webtorrent/extension/components/mediaViewer.tsx new file mode 100644 index 000000000000..4cfdb6701ef8 --- /dev/null +++ b/components/brave_webtorrent/extension/components/mediaViewer.tsx @@ -0,0 +1,72 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import * as React from 'react' + +// Constants +import { TorrentObj } from '../constants/webtorrentState' + +const SUPPORTED_VIDEO_EXTENSIONS = [ + 'm4v', + 'mkv', + 'mov', + 'mp4', + 'ogv', + 'webm' +] + +const SUPPORTED_AUDIO_EXTENSIONS = [ + 'aac', + 'mp3', + 'ogg', + 'wav', + 'm4a' +] + +// Given 'foo.txt', returns 'txt' +// Given eg. null, undefined, '', or 'README', returns null +const getExtension = (filename: string) => { + if (!filename) return null + const ix = filename.lastIndexOf('.') + if (ix < 0) return null + return filename.substring(ix + 1) +} + +interface Props { + torrent: TorrentObj + ix: number +} + +export default class MediaViewer extends React.PureComponent { + render () { + const { torrent, ix } = this.props + + const file = torrent.files ? torrent.files[ix] : undefined + const fileURL = torrent.serverURL && (torrent.serverURL + '/' + ix) + + const fileExt = file && getExtension(file.name) + const isVideo = fileExt ? SUPPORTED_VIDEO_EXTENSIONS.includes(fileExt) : false + const isAudio = fileExt ? SUPPORTED_AUDIO_EXTENSIONS.includes(fileExt) : false + + let content + if (!file || !torrent.serverURL) { + content =
Loading Media
+ } else if (isVideo) { + content =