diff --git a/browser/brave_wallet/BUILD.gn b/browser/brave_wallet/BUILD.gn index d9e71c4498a2..b55576c7c19f 100644 --- a/browser/brave_wallet/BUILD.gn +++ b/browser/brave_wallet/BUILD.gn @@ -21,6 +21,9 @@ source_set("brave_wallet") { "tx_service_factory.cc", "tx_service_factory.h", ] + if (!is_android) { + sources += [ "wallet_notification_helper.h" ] + } deps = [ "//brave/browser/profiles:util", "//brave/components/brave_wallet/browser", @@ -36,7 +39,6 @@ source_set("brave_wallet") { "//services/network/public/cpp", "//third_party/abseil-cpp:absl", ] - if (enable_extensions) { deps += [ "//brave/browser/extensions", @@ -172,6 +174,7 @@ source_set("unit_tests") { sources += [ "brave_wallet_tab_helper_unittest.cc", "ethereum_provider_impl_unittest.cc", + "notifications/wallet_notification_service_unittest.cc", "solana_provider_impl_unittest.cc", ] deps += [ diff --git a/browser/brave_wallet/notifications/sources.gni b/browser/brave_wallet/notifications/sources.gni new file mode 100644 index 000000000000..b3567c09ae02 --- /dev/null +++ b/browser/brave_wallet/notifications/sources.gni @@ -0,0 +1,16 @@ +# Copyright (c) 2022 The Brave Authors. All rights reserved. +# 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/. + +brave_browser_brave_wallet_sources = [] + +if (!is_android) { + brave_browser_brave_wallet_sources += [ + "//brave/browser/brave_wallet/notifications/wallet_notification_helper_impl.cc", + "//brave/browser/brave_wallet/notifications/wallet_notification_service.cc", + "//brave/browser/brave_wallet/notifications/wallet_notification_service.h", + "//brave/browser/brave_wallet/notifications/wallet_notification_service_factory.cc", + "//brave/browser/brave_wallet/notifications/wallet_notification_service_factory.h", + ] +} diff --git a/browser/brave_wallet/notifications/wallet_notification_helper_impl.cc b/browser/brave_wallet/notifications/wallet_notification_helper_impl.cc new file mode 100644 index 000000000000..9d606a136299 --- /dev/null +++ b/browser/brave_wallet/notifications/wallet_notification_helper_impl.cc @@ -0,0 +1,21 @@ +/* Copyright (c) 2022 The Brave Authors. All rights reserved. + * 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/browser/brave_wallet/wallet_notification_helper.h" + +#include "brave/browser/brave_wallet/notifications/wallet_notification_service_factory.h" +#include "brave/components/brave_wallet/browser/tx_service.h" + +namespace brave_wallet { + +void RegisterWalletNotificationService(content::BrowserContext* context, + TxService* tx_service) { + auto* notification_service = + WalletNotificationServiceFactory::GetInstance()->GetServiceForContext( + context); + tx_service->AddObserver(notification_service->GetReceiver()); +} + +} // namespace brave_wallet diff --git a/browser/brave_wallet/notifications/wallet_notification_service.cc b/browser/brave_wallet/notifications/wallet_notification_service.cc new file mode 100644 index 000000000000..856a5359ace5 --- /dev/null +++ b/browser/brave_wallet/notifications/wallet_notification_service.cc @@ -0,0 +1,106 @@ +/* Copyright (c) 2022 The Brave Authors. All rights reserved. + * 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/browser/brave_wallet/notifications/wallet_notification_service.h" + +#include +#include + +#include "base/strings/utf_string_conversions.h" +#include "brave/components/brave_wallet/browser/tx_service.h" +#include "chrome/browser/notifications/notification_display_service.h" +#include "chrome/browser/notifications/notification_display_service_factory.h" +#include "chrome/browser/profiles/profile.h" +#include "components/grit/brave_components_strings.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/models/image_model.h" +#include "ui/message_center/public/cpp/notification.h" +#include "ui/message_center/public/cpp/notification_types.h" +#include "ui/message_center/public/cpp/notifier_id.h" + +namespace { +int GetStatusTitle(brave_wallet::mojom::TransactionStatus status) { + switch (status) { + case brave_wallet::mojom::TransactionStatus::Confirmed: + return IDS_WALLET_TRANSACTION_STATUS_UPDATE_MESSAGE_TITLE_CONFIRMED; + case brave_wallet::mojom::TransactionStatus::Error: + return IDS_WALLET_TRANSACTION_STATUS_UPDATE_MESSAGE_TITLE_ERROR; + case brave_wallet::mojom::TransactionStatus::Dropped: + return IDS_WALLET_TRANSACTION_STATUS_UPDATE_MESSAGE_TITLE_DROPPED; + default: + break; + } + VLOG(1) << "No title for " << int(status) << " transaction status"; + return -1; +} + +std::unique_ptr CreateMessageCenterNotification( + const std::u16string& title, + const std::u16string& body, + const std::string& uuid, + const GURL& link) { + message_center::RichNotificationData notification_data; + // hack to prevent origin from showing in the notification + notification_data.context_message = u" "; + auto notification = std::make_unique( + message_center::NOTIFICATION_TYPE_SIMPLE, uuid, title, body, + ui::ImageModel(), std::u16string(), link, + message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT, + "service.wallet"), + notification_data, nullptr); + + return notification; +} + +void PushNotification(content::BrowserContext* context, + const std::string& uuid, + const std::string& from, + const std::u16string& title, + const std::u16string& body) { + auto notification = CreateMessageCenterNotification( + title, body, uuid, + GURL("brave://wallet/crypto/accounts/" + from + "#" + uuid)); + auto* profile = Profile::FromBrowserContext(context); + NotificationDisplayServiceFactory::GetForProfile(profile)->Display( + NotificationHandler::Type::SEND_TAB_TO_SELF, *notification, nullptr); +} + +} // namespace + +namespace brave_wallet { + +WalletNotificationService::WalletNotificationService( + content::BrowserContext* context) + : context_(context) {} + +WalletNotificationService::~WalletNotificationService() {} + +bool WalletNotificationService::ShouldDisplayUserNotification( + mojom::TransactionStatus status) { + return (status == mojom::TransactionStatus::Confirmed || + status == mojom::TransactionStatus::Error || + status == mojom::TransactionStatus::Dropped); +} + +void WalletNotificationService::DisplayUserNotification( + mojom::TransactionStatus status, + const std::string& address, + const std::string& tx_id) { + PushNotification(context_, tx_id, address, + l10n_util::GetStringUTF16(GetStatusTitle(status)), + l10n_util::GetStringFUTF16( + IDS_WALLET_TRANSACTION_STATUS_UPDATE_MESSAGE_TEXT, + base::UTF8ToUTF16(address))); +} + +void WalletNotificationService::OnTransactionStatusChanged( + mojom::TransactionInfoPtr tx_info) { + if (ShouldDisplayUserNotification(tx_info->tx_status)) { + DisplayUserNotification(tx_info->tx_status, tx_info->from_address, + tx_info->id); + } +} + +} // namespace brave_wallet diff --git a/browser/brave_wallet/notifications/wallet_notification_service.h b/browser/brave_wallet/notifications/wallet_notification_service.h new file mode 100644 index 000000000000..2d7aacd734a4 --- /dev/null +++ b/browser/brave_wallet/notifications/wallet_notification_service.h @@ -0,0 +1,55 @@ +/* Copyright (c) 2022 The Brave Authors. All rights reserved. + * 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/. */ + +#ifndef BRAVE_BROWSER_BRAVE_WALLET_NOTIFICATIONS_WALLET_NOTIFICATION_SERVICE_H_ +#define BRAVE_BROWSER_BRAVE_WALLET_NOTIFICATIONS_WALLET_NOTIFICATION_SERVICE_H_ + +#include + +#include "base/memory/raw_ptr.h" +#include "brave/components/brave_wallet/common/brave_wallet.mojom.h" +#include "components/keyed_service/core/keyed_service.h" +#include "mojo/public/cpp/bindings/pending_remote.h" +#include "mojo/public/cpp/bindings/receiver.h" + +namespace content { +class BrowserContext; +} // namespace content + +namespace brave_wallet { + +class WalletNotificationService : public KeyedService, + public mojom::TxServiceObserver { + public: + explicit WalletNotificationService(content::BrowserContext* context); + ~WalletNotificationService() override; + WalletNotificationService(const WalletNotificationService&) = delete; + WalletNotificationService operator=(const WalletNotificationService&) = + delete; + + // mojom::TxServiceObserver + void OnNewUnapprovedTx(mojom::TransactionInfoPtr tx_info) override {} + void OnUnapprovedTxUpdated(mojom::TransactionInfoPtr tx_info) override {} + void OnTransactionStatusChanged(mojom::TransactionInfoPtr tx_info) override; + + mojo::PendingRemote GetReceiver() { + return tx_observer_receiver_.BindNewPipeAndPassRemote(); + } + + private: + friend class WalletNotificationServiceUnitTest; + + bool ShouldDisplayUserNotification(mojom::TransactionStatus status); + void DisplayUserNotification(mojom::TransactionStatus status, + const std::string& address, + const std::string& tx_id); + + raw_ptr context_; + mojo::Receiver tx_observer_receiver_{this}; +}; + +} // namespace brave_wallet + +#endif // BRAVE_BROWSER_BRAVE_WALLET_NOTIFICATIONS_WALLET_NOTIFICATION_SERVICE_H_ diff --git a/browser/brave_wallet/notifications/wallet_notification_service_factory.cc b/browser/brave_wallet/notifications/wallet_notification_service_factory.cc new file mode 100644 index 000000000000..0a5ed515993e --- /dev/null +++ b/browser/brave_wallet/notifications/wallet_notification_service_factory.cc @@ -0,0 +1,50 @@ +/* Copyright (c) 2022 The Brave Authors. All rights reserved. + * 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/browser/brave_wallet/notifications/wallet_notification_service_factory.h" + +#include + +#include "brave/browser/brave_wallet/brave_wallet_context_utils.h" +#include "brave/browser/brave_wallet/notifications/wallet_notification_service.h" +#include "brave/browser/brave_wallet/tx_service_factory.h" +#include "chrome/browser/notifications/notification_display_service_factory.h" +#include "components/keyed_service/content/browser_context_dependency_manager.h" + +namespace brave_wallet { + +// static +WalletNotificationServiceFactory* +WalletNotificationServiceFactory::GetInstance() { + return base::Singleton::get(); +} + +WalletNotificationServiceFactory::WalletNotificationServiceFactory() + : BrowserContextKeyedServiceFactory( + "WalletNotificationService", + BrowserContextDependencyManager::GetInstance()) { + DependsOn(NotificationDisplayServiceFactory::GetInstance()); + DependsOn(brave_wallet::TxServiceFactory::GetInstance()); +} + +WalletNotificationServiceFactory::~WalletNotificationServiceFactory() {} + +KeyedService* WalletNotificationServiceFactory::BuildServiceInstanceFor( + content::BrowserContext* context) const { + return new WalletNotificationService(context); +} + +// static +WalletNotificationService* +WalletNotificationServiceFactory::GetServiceForContext( + content::BrowserContext* context) { + if (!IsAllowedForContext(context)) { + return nullptr; + } + return static_cast( + GetInstance()->GetServiceForBrowserContext(context, true)); +} + +} // namespace brave_wallet diff --git a/browser/brave_wallet/notifications/wallet_notification_service_factory.h b/browser/brave_wallet/notifications/wallet_notification_service_factory.h new file mode 100644 index 000000000000..0fc81f237bb6 --- /dev/null +++ b/browser/brave_wallet/notifications/wallet_notification_service_factory.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2022 The Brave Authors. All rights reserved. + * 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/. */ + +#ifndef BRAVE_BROWSER_BRAVE_WALLET_NOTIFICATIONS_WALLET_NOTIFICATION_SERVICE_FACTORY_H_ +#define BRAVE_BROWSER_BRAVE_WALLET_NOTIFICATIONS_WALLET_NOTIFICATION_SERVICE_FACTORY_H_ + +#include "base/memory/singleton.h" +#include "brave/browser/brave_wallet/notifications/wallet_notification_service.h" +#include "components/keyed_service/content/browser_context_keyed_service_factory.h" + +namespace brave_wallet { + +// Singleton that owns all WalletNotificationService and associates them with +// BrowserContext. +class WalletNotificationServiceFactory + : public BrowserContextKeyedServiceFactory { + public: + WalletNotificationServiceFactory(const WalletNotificationServiceFactory&) = + delete; + WalletNotificationServiceFactory& operator=( + const WalletNotificationServiceFactory&) = delete; + + static WalletNotificationServiceFactory* GetInstance(); + static WalletNotificationService* GetServiceForContext( + content::BrowserContext* context); + + private: + friend struct base::DefaultSingletonTraits; + + WalletNotificationServiceFactory(); + ~WalletNotificationServiceFactory() override; + + // BrowserContextKeyedServiceFactory: + KeyedService* BuildServiceInstanceFor( + content::BrowserContext* context) const override; +}; + +} // namespace brave_wallet + +#endif // BRAVE_BROWSER_BRAVE_WALLET_NOTIFICATIONS_WALLET_NOTIFICATION_SERVICE_FACTORY_H_ diff --git a/browser/brave_wallet/notifications/wallet_notification_service_unittest.cc b/browser/brave_wallet/notifications/wallet_notification_service_unittest.cc new file mode 100644 index 000000000000..59d3f80ee139 --- /dev/null +++ b/browser/brave_wallet/notifications/wallet_notification_service_unittest.cc @@ -0,0 +1,107 @@ +/* Copyright (c) 2022 The Brave Authors. All rights reserved. + * 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/browser/brave_wallet/notifications/wallet_notification_service.h" + +#include +#include +#include + +#include "brave/components/brave_wallet/browser/eth_transaction.h" +#include "brave/components/brave_wallet/browser/eth_tx_meta.h" +#include "brave/components/brave_wallet/browser/json_rpc_service.h" +#include "brave/components/brave_wallet/browser/keyring_service.h" +#include "brave/components/brave_wallet/browser/tx_service.h" +#include "brave/components/brave_wallet/common/brave_wallet.mojom.h" +#include "chrome/browser/notifications/notification_display_service_tester.h" +#include "chrome/test/base/testing_profile.h" +#include "content/public/test/browser_task_environment.h" +#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" +#include "services/network/test/test_url_loader_factory.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/base/l10n/l10n_util.h" + +namespace brave_wallet { + +class WalletNotificationServiceUnitTest : public testing::Test { + public: + WalletNotificationServiceUnitTest() + : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME), + shared_url_loader_factory_( + base::MakeRefCounted( + &url_loader_factory_)) {} + + void SetUp() override { + json_rpc_service_.reset( + new JsonRpcService(shared_url_loader_factory_, prefs())); + keyring_service_.reset( + new KeyringService(json_rpc_service_.get(), prefs())); + tx_service_.reset(new TxService(json_rpc_service_.get(), + keyring_service_.get(), prefs())); + notification_service_.reset(new WalletNotificationService(profile())); + tester_ = std::make_unique(profile()); + } + Profile* profile() { return &profile_; } + PrefService* prefs() { return profile_.GetPrefs(); } + + bool ShouldDisplayNotifications(mojom::TransactionStatus status) { + return notification_service_->ShouldDisplayUserNotification(status); + } + + bool WasNotificationDisplayedOnStatusChange(mojom::TransactionStatus status) { + std::unique_ptr tx = + std::make_unique(*EthTransaction::FromTxData( + mojom::TxData::New("0x01", "0x4a817c800", "0x5208", + "0x3535353535353535353535353535353535353535", + "0x0de0b6b3a7640000", std::vector()))); + EthTxMeta meta(std::move(tx)); + meta.set_status(status); + notification_service_->OnTransactionStatusChanged(meta.ToTransactionInfo()); + auto notification = tester_->GetNotification(meta.id()); + tester_->RemoveAllNotifications(NotificationHandler::Type::SEND_TAB_TO_SELF, + true /* by_user */); + return notification.has_value(); + } + + private: + content::BrowserTaskEnvironment task_environment_; + std::unique_ptr tester_; + TestingProfile profile_; + network::TestURLLoaderFactory url_loader_factory_; + scoped_refptr shared_url_loader_factory_; + std::unique_ptr notification_service_; + std::unique_ptr json_rpc_service_; + std::unique_ptr keyring_service_; + std::unique_ptr tx_service_; +}; + +TEST_F(WalletNotificationServiceUnitTest, ShouldShowNotifications) { + EXPECT_TRUE(ShouldDisplayNotifications(mojom::TransactionStatus::Confirmed)); + EXPECT_TRUE(ShouldDisplayNotifications(mojom::TransactionStatus::Error)); + EXPECT_TRUE(ShouldDisplayNotifications(mojom::TransactionStatus::Dropped)); + + EXPECT_FALSE(ShouldDisplayNotifications(mojom::TransactionStatus::Approved)); + EXPECT_FALSE(ShouldDisplayNotifications(mojom::TransactionStatus::Rejected)); + EXPECT_FALSE(ShouldDisplayNotifications(mojom::TransactionStatus::Submitted)); +} + +TEST_F(WalletNotificationServiceUnitTest, TransactionStatusChanged) { + EXPECT_TRUE(WasNotificationDisplayedOnStatusChange( + mojom::TransactionStatus::Confirmed)); + EXPECT_TRUE( + WasNotificationDisplayedOnStatusChange(mojom::TransactionStatus::Error)); + EXPECT_TRUE(WasNotificationDisplayedOnStatusChange( + mojom::TransactionStatus::Dropped)); + + EXPECT_FALSE(WasNotificationDisplayedOnStatusChange( + mojom::TransactionStatus::Approved)); + EXPECT_FALSE(WasNotificationDisplayedOnStatusChange( + mojom::TransactionStatus::Rejected)); + EXPECT_FALSE(WasNotificationDisplayedOnStatusChange( + mojom::TransactionStatus::Submitted)); +} + +} // namespace brave_wallet diff --git a/browser/brave_wallet/tx_service_factory.cc b/browser/brave_wallet/tx_service_factory.cc index de064975b014..ce6874fe0cb8 100644 --- a/browser/brave_wallet/tx_service_factory.cc +++ b/browser/brave_wallet/tx_service_factory.cc @@ -20,6 +20,10 @@ #include "content/public/browser/storage_partition.h" #include "services/network/public/cpp/shared_url_loader_factory.h" +#if !BUILDFLAG(IS_ANDROID) +#include "brave/browser/brave_wallet/wallet_notification_helper.h" +#endif + namespace brave_wallet { // static @@ -141,9 +145,14 @@ TxServiceFactory::~TxServiceFactory() {} KeyedService* TxServiceFactory::BuildServiceInstanceFor( content::BrowserContext* context) const { - return new TxService(JsonRpcServiceFactory::GetServiceForContext(context), - KeyringServiceFactory::GetServiceForContext(context), - user_prefs::UserPrefs::Get(context)); + auto* tx_service = + new TxService(JsonRpcServiceFactory::GetServiceForContext(context), + KeyringServiceFactory::GetServiceForContext(context), + user_prefs::UserPrefs::Get(context)); +#if !BUILDFLAG(IS_ANDROID) + RegisterWalletNotificationService(context, tx_service); +#endif + return tx_service; } content::BrowserContext* TxServiceFactory::GetBrowserContextToUse( diff --git a/browser/brave_wallet/wallet_notification_helper.h b/browser/brave_wallet/wallet_notification_helper.h new file mode 100644 index 000000000000..f3f16094a885 --- /dev/null +++ b/browser/brave_wallet/wallet_notification_helper.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2022 The Brave Authors. All rights reserved. + * 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/. */ + +#ifndef BRAVE_BROWSER_BRAVE_WALLET_WALLET_NOTIFICATION_HELPER_H_ +#define BRAVE_BROWSER_BRAVE_WALLET_WALLET_NOTIFICATION_HELPER_H_ + +namespace content { +class BrowserContext; +} // namespace content + +namespace brave_wallet { + +class TxService; + +void RegisterWalletNotificationService(content::BrowserContext* context, + TxService* service); + +} // namespace brave_wallet + +#endif // BRAVE_BROWSER_BRAVE_WALLET_WALLET_NOTIFICATION_HELPER_H_ diff --git a/browser/sources.gni b/browser/sources.gni index 39c7085c8025..0bacf080a731 100644 --- a/browser/sources.gni +++ b/browser/sources.gni @@ -13,6 +13,7 @@ import("//brave/browser/brave_shields/sources.gni") import("//brave/browser/brave_stats/sources.gni") import("//brave/browser/brave_vpn/sources.gni") import("//brave/browser/brave_wallet/android/sources.gni") +import("//brave/browser/brave_wallet/notifications/sources.gni") import("//brave/browser/browsing_data/sources.gni") import("//brave/browser/component_updater/sources.gni") import("//brave/browser/crypto_dot_com/sources.gni") @@ -388,6 +389,7 @@ brave_chrome_browser_sources += brave_browser_autocomplete_sources brave_chrome_browser_sources += brave_browser_binance_sources brave_chrome_browser_sources += brave_browser_brave_adaptive_captcha_sources brave_chrome_browser_sources += brave_browser_brave_ads_sources +brave_chrome_browser_sources += brave_browser_brave_wallet_sources brave_chrome_browser_sources += brave_browser_brave_news_sources brave_chrome_browser_sources += brave_browser_brave_vpn_sources brave_chrome_browser_sources += brave_browser_brave_rewards_sources diff --git a/components/resources/wallet_strings.grdp b/components/resources/wallet_strings.grdp index 4e086119cf9f..0ce275bbe1b3 100644 --- a/components/resources/wallet_strings.grdp +++ b/components/resources/wallet_strings.grdp @@ -457,4 +457,8 @@ Transaction data 'to' must be specified Transaction data 'from' must be a valid Filecoin address Ledger device locked + Click here to check transaction details for $10xABC + Transaction confirmed + Transaction error + Transaction dropped