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/brave_content_browser_client.cc b/browser/brave_content_browser_client.cc index 085b21d9ff35..98294693b60b 100644 --- a/browser/brave_content_browser_client.cc +++ b/browser/brave_content_browser_client.cc @@ -7,6 +7,7 @@ #include "brave/browser/brave_browser_main_extra_parts.h" #include "brave/common/webui_url_constants.h" #include "brave/components/brave_shields/browser/brave_shields_web_contents_observer.h" +#include "brave/components/brave_webtorrent/browser/content_browser_client_helper.h" #include "brave/components/content_settings/core/browser/brave_cookie_settings.h" #include "chrome/browser/content_settings/tab_specific_content_settings.h" #include "chrome/browser/profiles/profile_io_data.h" @@ -80,6 +81,8 @@ void BraveContentBrowserClient::BrowserURLHandlerCreated( content::BrowserURLHandler* handler) { // Insert handler for chrome://newtab so that we handle it // before anything else can. + handler->AddHandlerPair(&webtorrent::HandleMagnetURLRewrite, + content::BrowserURLHandler::null_handler()); handler->AddHandlerPair(&HandleURLRewrite, &HandleURLReverseRewrite); ChromeContentBrowserClient::BrowserURLHandlerCreated(handler); @@ -120,6 +123,25 @@ BraveContentBrowserClient::AllowWebBluetooth( return content::ContentBrowserClient::AllowWebBluetoothResult::BLOCK_GLOBALLY_DISABLED; } +bool BraveContentBrowserClient::HandleExternalProtocol( + const GURL& url, + content::ResourceRequestInfo::WebContentsGetter web_contents_getter, + int child_id, + content::NavigationUIData* navigation_data, + bool is_main_frame, + ui::PageTransition page_transition, + bool has_user_gesture) { + + if (webtorrent::HandleMagnetProtocol(url, web_contents_getter, + page_transition, has_user_gesture)) { + return true; + } + + return ChromeContentBrowserClient::HandleExternalProtocol( + url, web_contents_getter, child_id, navigation_data, is_main_frame, + page_transition, has_user_gesture); +} + bool BraveContentBrowserClient::AllowSetCookie( const GURL& url, const GURL& first_party, diff --git a/browser/brave_content_browser_client.h b/browser/brave_content_browser_client.h index c7be70d2766d..66fbc10c5311 100644 --- a/browser/brave_content_browser_client.h +++ b/browser/brave_content_browser_client.h @@ -32,6 +32,15 @@ class BraveContentBrowserClient : public ChromeContentBrowserClient { int render_process_id, int render_frame_id) override; + bool HandleExternalProtocol( + const GURL& url, + content::ResourceRequestInfo::WebContentsGetter web_contents_getter, + int child_id, + content::NavigationUIData* navigation_data, + bool is_main_frame, + ui::PageTransition page_transition, + bool has_user_gesture) override; + content::ContentBrowserClient::AllowWebBluetoothResult AllowWebBluetooth( content::BrowserContext* browser_context, const url::Origin& requesting_origin, diff --git a/browser/brave_content_browser_client_browsertest.cc b/browser/brave_content_browser_client_browsertest.cc index 230e6bd7402e..6e0c01d694d4 100644 --- a/browser/brave_content_browser_client_browsertest.cc +++ b/browser/brave_content_browser_client_browsertest.cc @@ -4,12 +4,60 @@ #include +#include "base/path_service.h" +#include "brave/browser/brave_content_browser_client.h" +#include "brave/common/brave_paths.h" #include "chrome/browser/ui/browser.h" +#include "chrome/common/chrome_content_client.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/ui_test_utils.h" +#include "content/public/browser/navigation_entry.h" #include "content/public/test/browser_test_utils.h" +#include "net/dns/mock_host_resolver.h" -using BraveContentBrowserClientTest = InProcessBrowserTest; +class BraveContentBrowserClientTest : public InProcessBrowserTest { + public: + void SetUpOnMainThread() override { + InProcessBrowserTest::SetUpOnMainThread(); + + content_client_.reset(new ChromeContentClient); + content::SetContentClient(content_client_.get()); + browser_content_client_.reset(new BraveContentBrowserClient()); + content::SetBrowserClientForTesting(browser_content_client_.get()); + + host_resolver()->AddRule("*", "127.0.0.1"); + content::SetupCrossSiteRedirector(embedded_test_server()); + + brave::RegisterPathProvider(); + base::FilePath test_data_dir; + base::PathService::Get(brave::DIR_TEST_DATA, &test_data_dir); + embedded_test_server()->ServeFilesFromDirectory(test_data_dir); + + ASSERT_TRUE(embedded_test_server()->Start()); + + url_ = embedded_test_server()->GetURL("a.com", "/magnet.html"); + magnet_url_ = GURL("magnet:?xt=urn:btih:dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c&dn=Big+Buck+Bunny&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fbig-buck-bunny.torrent"); + extension_url_ = GURL("chrome-extension://lgjmpdmojkpocjcopdikifhejkkjglho/extension/brave_webtorrent.html?magnet%3A%3Fxt%3Durn%3Abtih%3Add8255ecdc7ca55fb0bbf81323d87062db1f6d1c%26dn%3DBig%2BBuck%2BBunny%26tr%3Dudp%253A%252F%252Fexplodie.org%253A6969%26tr%3Dudp%253A%252F%252Ftracker.coppersurfer.tk%253A6969%26tr%3Dudp%253A%252F%252Ftracker.empire-js.us%253A1337%26tr%3Dudp%253A%252F%252Ftracker.leechers-paradise.org%253A6969%26tr%3Dudp%253A%252F%252Ftracker.opentrackr.org%253A1337%26tr%3Dwss%253A%252F%252Ftracker.btorrent.xyz%26tr%3Dwss%253A%252F%252Ftracker.fastcast.nz%26tr%3Dwss%253A%252F%252Ftracker.openwebtorrent.com%26ws%3Dhttps%253A%252F%252Fwebtorrent.io%252Ftorrents%252F%26xs%3Dhttps%253A%252F%252Fwebtorrent.io%252Ftorrents%252Fbig-buck-bunny.torrent"); + } + + void TearDown() override { + browser_content_client_.reset(); + content_client_.reset(); + } + + const GURL& url() { return url_; } + const GURL& magnet_url() { return magnet_url_; } + const GURL& extension_url() { return extension_url_; } + + private: + GURL url_; + GURL magnet_url_; + GURL extension_url_; + ContentSettingsPattern top_level_page_pattern_; + ContentSettingsPattern empty_pattern_; + std::unique_ptr content_client_; + std::unique_ptr browser_content_client_; +}; IN_PROC_BROWSER_TEST_F(BraveContentBrowserClientTest, CanLoadChromeURL) { GURL chrome_settings_url("chrome://settings/"); @@ -41,3 +89,32 @@ IN_PROC_BROWSER_TEST_F(BraveContentBrowserClientTest, CanLoadCustomBravePages) { url.spec().c_str()); }); } + +IN_PROC_BROWSER_TEST_F(BraveContentBrowserClientTest, RewriteMagnetURLURLBar) { + content::WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents(); + + ui_test_utils::NavigateToURL(browser(), magnet_url()); + ASSERT_TRUE(WaitForLoadStop(contents)); + EXPECT_STREQ(contents->GetLastCommittedURL().spec().c_str(), + magnet_url().spec().c_str()) << "URL visible to users should stay as the magnet URL"; + content::NavigationEntry* entry = contents->GetController().GetLastCommittedEntry(); + EXPECT_STREQ(entry->GetURL().spec().c_str(), + extension_url().spec().c_str()) << "Real URL should be extension URL"; +} + +IN_PROC_BROWSER_TEST_F(BraveContentBrowserClientTest, RewriteMagnetURLLink) { + content::WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents(); + ui_test_utils::NavigateToURL(browser(), url()); + ASSERT_TRUE(WaitForLoadStop(contents)); + bool value; + EXPECT_TRUE(ExecuteScriptAndExtractBool(contents, "clickMagnetLink();", + &value)); + EXPECT_TRUE(value); + ASSERT_TRUE(WaitForLoadStop(contents)); + + EXPECT_STREQ(contents->GetLastCommittedURL().spec().c_str(), + magnet_url().spec().c_str()) << "URL visible to users should stay as the magnet URL"; + content::NavigationEntry* entry = contents->GetController().GetLastCommittedEntry(); + EXPECT_STREQ(entry->GetURL().spec().c_str(), + extension_url().spec().c_str()) << "Real URL should be extension URL"; +} 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/chrome/browser/profiles/profile_io_data.cc b/chromium_src/chrome/browser/profiles/profile_io_data.cc new file mode 100644 index 000000000000..57fa82ae53fe --- /dev/null +++ b/chromium_src/chrome/browser/profiles/profile_io_data.cc @@ -0,0 +1,3 @@ +#include "brave/browser/net/brave_profile_network_delegate.h" + +#include "../../../../../../chrome/browser/profiles/profile_io_data.cc" diff --git a/common/extensions/api/BUILD.gn b/common/extensions/api/BUILD.gn index 1f7a241b1abf..61554e05d1b9 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/common/url_constants.cc b/common/url_constants.cc index 666d8f70b36f..90018b6d32e8 100644 --- a/common/url_constants.cc +++ b/common/url_constants.cc @@ -5,5 +5,6 @@ #include "brave/common/url_constants.h" const char kBraveUIScheme[] = "brave"; +const char kMagnetScheme[] = "magnet"; const char kWidevineMoreInfoURL[] = "https://www.eff.org/issues/drm"; const char kWidevineTOS[] = "https://policies.google.com/terms"; diff --git a/common/url_constants.h b/common/url_constants.h index c2fa96fd9cab..fca2e0bd02a4 100644 --- a/common/url_constants.h +++ b/common/url_constants.h @@ -6,6 +6,7 @@ #define BRAVE_COMMON_URL_CONSTANTS_H_ extern const char kBraveUIScheme[]; +extern const char kMagnetScheme[]; extern const char kWidevineMoreInfoURL[]; extern const char kWidevineTOS[]; 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/browser/content_browser_client_helper.h b/components/brave_webtorrent/browser/content_browser_client_helper.h new file mode 100644 index 000000000000..b7de17e33091 --- /dev/null +++ b/components/brave_webtorrent/browser/content_browser_client_helper.h @@ -0,0 +1,84 @@ +/* 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 "base/strings/strcat.h" +#include "base/strings/string_util.h" +#include "brave/common/url_constants.h" +#include "brave/common/extensions/extension_constants.h" +#include "chrome/browser/external_protocol/external_protocol_handler.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" +#include "extensions/browser/extension_registry.h" +#include "extensions/common/constants.h" +#include "extensions/common/extension_set.h" +#include "net/base/escape.h" + +namespace webtorrent { + +static GURL TranslateMagnetURL(const GURL& url) { + GURL extension_page_url( + base::StrCat({extensions::kExtensionScheme, "://", + brave_webtorrent_extension_id, + "/extension/brave_webtorrent.html?%s"})); + std::string translatedSpec(extension_page_url.spec()); + base::ReplaceFirstSubstringAfterOffset( + &translatedSpec, 0, "%s", + net::EscapeQueryParamValue(url.spec(), true)); + return GURL(translatedSpec); +} + +static bool IsWebtorrentInstalled(content::BrowserContext* browser_context) { + extensions::ExtensionRegistry* registry = + extensions::ExtensionRegistry::Get(browser_context); + return registry->enabled_extensions().Contains( + brave_webtorrent_extension_id); +} + +static void LoadOrLaunchMagnetURL( + const GURL& url, + const content::ResourceRequestInfo::WebContentsGetter& web_contents_getter, + ui::PageTransition page_transition, + bool has_user_gesture) { + content::WebContents* web_contents = web_contents_getter.Run(); + if (!web_contents) + return; + + if (IsWebtorrentInstalled(web_contents->GetBrowserContext())) { + web_contents->GetController().LoadURL(url, content::Referrer(), + page_transition, std::string()); + } else { + ExternalProtocolHandler::LaunchUrl( + url, web_contents->GetRenderViewHost()->GetProcess()->GetID(), + web_contents->GetRenderViewHost()->GetRoutingID(), page_transition, + has_user_gesture); + } +} + +static bool HandleMagnetURLRewrite(GURL* url, + content::BrowserContext* browser_context) { + if (IsWebtorrentInstalled(browser_context) && url->SchemeIs(kMagnetScheme)) { + *url = TranslateMagnetURL(*url); + return true; + } + + return false; +} + +static bool HandleMagnetProtocol( + const GURL& url, + content::ResourceRequestInfo::WebContentsGetter web_contents_getter, + ui::PageTransition page_transition, + bool has_user_gesture) { + if (url.SchemeIs(kMagnetScheme)) { + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, + base::BindOnce(&LoadOrLaunchMagnetURL, url, web_contents_getter, + page_transition, has_user_gesture)); + return true; + } + + return false; +} + +} 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..7ad0e73e8d65 --- /dev/null +++ b/components/brave_webtorrent/extension/background/reducers/webtorrent_reducer.ts @@ -0,0 +1,217 @@ +/* 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 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 (parsedURL.protocol === 'magnet:') { // parse torrent + const torrentId = parsedURL.href + 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..a0a11f1fad47 --- /dev/null +++ b/components/brave_webtorrent/extension/background/store.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 { createStore } from 'redux' +import { wrapStore } from 'react-chrome-redux' + +import reducers from './reducers' + +const store = createStore(reducers) + +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 =