From 4566e870f537b68662b95dcdb0e7523f7cad5175 Mon Sep 17 00:00:00 2001 From: Simon Hong Date: Thu, 20 Apr 2023 18:27:11 +0900 Subject: [PATCH] Merge pull request #18009 from brave/refactor_region_data_handling Move region data handling from BraveVpnService to BraveOSVpnConnectionAPI --- components/brave_vpn/browser/BUILD.gn | 1 + .../brave_vpn/browser/brave_vpn_service.cc | 393 ++++-------------- .../brave_vpn/browser/brave_vpn_service.h | 34 +- .../browser/brave_vpn_service_helper.cc | 66 --- .../browser/brave_vpn_service_helper.h | 4 - .../browser/brave_vpn_service_unittest.cc | 145 +++---- .../brave_vpn/browser/connection/BUILD.gn | 4 + .../connection/brave_vpn_os_connection_api.h | 7 + .../brave_vpn_os_connection_api_base.cc | 66 ++- .../brave_vpn_os_connection_api_base.h | 15 +- .../brave_vpn_os_connection_api_unittest.cc | 261 +++++++++++- .../brave_vpn_region_data_manager.cc | 356 ++++++++++++++++ .../brave_vpn_region_data_manager.h | 88 ++++ .../brave_vpn/common/brave_vpn_utils.cc | 1 + components/brave_vpn/common/pref_names.h | 3 + 15 files changed, 932 insertions(+), 512 deletions(-) create mode 100644 components/brave_vpn/browser/connection/brave_vpn_region_data_manager.cc create mode 100644 components/brave_vpn/browser/connection/brave_vpn_region_data_manager.h diff --git a/components/brave_vpn/browser/BUILD.gn b/components/brave_vpn/browser/BUILD.gn index 3d34691137ce..dd6819e766d0 100644 --- a/components/brave_vpn/browser/BUILD.gn +++ b/components/brave_vpn/browser/BUILD.gn @@ -21,6 +21,7 @@ static_library("browser") { "api", "connection", "connection:api", + "connection:common", "//base", "//brave/components/brave_vpn/common", "//brave/components/brave_vpn/common/mojom", diff --git a/components/brave_vpn/browser/brave_vpn_service.cc b/components/brave_vpn/browser/brave_vpn_service.cc index 45364596a035..7423c0a76066 100644 --- a/components/brave_vpn/browser/brave_vpn_service.cc +++ b/components/brave_vpn/browser/brave_vpn_service.cc @@ -19,6 +19,7 @@ #include "base/time/time.h" #include "brave/components/brave_vpn/browser/api/brave_vpn_api_helper.h" #include "brave/components/brave_vpn/browser/brave_vpn_service_helper.h" +#include "brave/components/brave_vpn/browser/connection/brave_vpn_region_data_manager.h" #include "brave/components/brave_vpn/common/brave_vpn_constants.h" #include "brave/components/brave_vpn/common/brave_vpn_utils.h" #include "brave/components/brave_vpn/common/pref_names.h" @@ -48,17 +49,12 @@ BraveVpnService::BraveVpnService( : local_prefs_(local_prefs), profile_prefs_(profile_prefs), skus_service_getter_(skus_service_getter), - api_request_(url_loader_factory) { + api_request_(new BraveVpnAPIRequest(url_loader_factory)) { DCHECK(IsBraveVPNEnabled()); #if !BUILDFLAG(IS_ANDROID) + DCHECK(connection_api); connection_api_ = connection_api; - observed_.Observe(GetBraveVPNConnectionAPI()); - - pref_change_registrar_.Init(local_prefs_); - pref_change_registrar_.Add( - prefs::kBraveVPNSelectedRegion, - base::BindRepeating(&BraveVpnService::OnPreferenceChanged, - base::Unretained(this))); + observed_.Observe(connection_api_); #endif // !BUILDFLAG(IS_ANDROID) @@ -76,18 +72,17 @@ void BraveVpnService::CheckInitialState() { SetPurchasedState(GetCurrentEnvironment(), PurchasedState::PURCHASED); // Android has its own region data managing logic. #else - LoadCachedRegionData(); - if (regions_.empty()) { + if (connection_api_->GetRegionDataManager().IsRegionDataReady()) { + SetPurchasedState(GetCurrentEnvironment(), PurchasedState::PURCHASED); + } else { SetPurchasedState(GetCurrentEnvironment(), PurchasedState::LOADING); // Not sure this can happen when user is already purchased user. // To make sure before set as purchased user, however, trying region fetch - // and then set as a purchased user when we get valid region data. - FetchRegionData(false); - } else { - SetPurchasedState(GetCurrentEnvironment(), PurchasedState::PURCHASED); + // and then set as a purchased user after we get valid region data. + wait_region_data_ready_ = true; } - ScheduleBackgroundRegionDataFetch(); + connection_api_->GetRegionDataManager().FetchRegionDataIfNeeded(); #endif } else if (HasValidSkusCredential(local_prefs_)) { // If we have valid skus creds during the startup, we can try to get subs @@ -122,19 +117,6 @@ void BraveVpnService::BindInterface( } #if !BUILDFLAG(IS_ANDROID) -void BraveVpnService::ScheduleBackgroundRegionDataFetch() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (region_data_update_timer_.IsRunning()) - return; - - // Try to update region list every 5h. - constexpr int kRegionDataUpdateIntervalInHours = 5; - region_data_update_timer_.Start( - FROM_HERE, base::Hours(kRegionDataUpdateIntervalInHours), - base::BindRepeating(&BraveVpnService::FetchRegionData, - base::Unretained(this), true)); -} - void BraveVpnService::OnConnectionStateChanged(mojom::ConnectionState state) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); VLOG(2) << __func__ << " " << state; @@ -154,7 +136,7 @@ void BraveVpnService::OnConnectionStateChanged(mojom::ConnectionState state) { mojom::ConnectionState BraveVpnService::GetConnectionState() const { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - return GetBraveVPNConnectionAPI()->GetConnectionState(); + return connection_api_->GetConnectionState(); } bool BraveVpnService::IsConnected() const { @@ -164,7 +146,7 @@ bool BraveVpnService::IsConnected() const { void BraveVpnService::RemoveVPNConnection() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); VLOG(2) << __func__; - GetBraveVPNConnectionAPI()->RemoveVPNConnection(); + connection_api_->RemoveVPNConnection(); } void BraveVpnService::Connect() { @@ -174,7 +156,7 @@ void BraveVpnService::Connect() { return; } - GetBraveVPNConnectionAPI()->Connect(); + connection_api_->Connect(); } void BraveVpnService::Disconnect() { @@ -184,232 +166,58 @@ void BraveVpnService::Disconnect() { return; } - GetBraveVPNConnectionAPI()->Disconnect(); + connection_api_->Disconnect(); } -void BraveVpnService::ToggleConnection() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - - if (!is_purchased_user()) { +void BraveVpnService::OnRegionDataReady(bool success) { + VLOG(2) << __func__ << " success - " << success << ", is waiting? " + << wait_region_data_ready_; + if (!wait_region_data_ready_) { return; } - GetBraveVPNConnectionAPI()->ToggleConnection(); -} - -void BraveVpnService::GetConnectionState(GetConnectionStateCallback callback) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - const auto state = GetBraveVPNConnectionAPI()->GetConnectionState(); - VLOG(2) << __func__ << " : " << state; - std::move(callback).Run(state); -} - -void BraveVpnService::FetchRegionData(bool background_fetch) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - // Only do background fetching for purchased user. - if (background_fetch && !is_purchased_user()) - return; - - VLOG(2) << __func__ - << (background_fetch ? " : Start fetching region data in background" - : " : Start fetching region data"); - - // Unretained is safe here becasue this class owns |api_request_|. - api_request_.GetAllServerRegions( - base::BindOnce(&BraveVpnService::OnFetchRegionList, - base::Unretained(this), background_fetch)); -} - -void BraveVpnService::LoadCachedRegionData() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - - // Already loaded from cache. - if (!regions_.empty()) - return; - - // Empty device region means it's initial state. - if (GetDeviceRegion().empty()) - return; - - auto* preference = local_prefs_->FindPreference(prefs::kBraveVPNRegionList); - DCHECK(preference); - // Early return when we don't have any cached region data. - if (preference->IsDefaultValue()) - return; - - // If cached one is outdated, don't use it. - if (!ValidateCachedRegionData(preference->GetValue()->GetList())) { - VLOG(2) << __func__ << " : Cached data is outdate. Will get fetch latest."; - return; - } + wait_region_data_ready_ = false; - if (ParseAndCacheRegionList(preference->GetValue()->GetList())) { - VLOG(2) << __func__ << " : Loaded cached region list"; + // Happened weird state while waiting region data. + // Don't update purchased if current state is not loading state. + if (purchased_state_ != PurchasedState::LOADING) { return; } - VLOG(2) << __func__ << " : Failed to load cached region list"; + SetPurchasedState(GetCurrentEnvironment(), success ? PurchasedState::PURCHASED + : PurchasedState::FAILED); } -void BraveVpnService::SetRegionListToPrefs() { - DCHECK(!regions_.empty()); - - base::Value::List regions_list; - for (const auto& region : regions_) { - regions_list.Append(GetValueFromRegion(region)); +void BraveVpnService::OnSelectedRegionChanged(const std::string& region_name) { + const auto region_ptr = GetRegionPtrWithNameFromRegionList( + region_name, connection_api_->GetRegionDataManager().GetRegions()); + for (const auto& obs : observers_) { + obs->OnSelectedRegionChanged(region_ptr.Clone()); } - local_prefs_->Set(prefs::kBraveVPNRegionList, - base::Value(std::move(regions_list))); } -void BraveVpnService::OnFetchRegionList(bool background_fetch, - const std::string& region_list, - bool success) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - // Don't update purchased state during the background fetching. - - if (!background_fetch && !success) { - VLOG(2) << "Failed to get region list"; - SetPurchasedState(GetCurrentEnvironment(), PurchasedState::FAILED); - return; - } - - absl::optional value = base::JSONReader::Read(region_list); - if (value && value->is_list()) { - if (background_fetch) { - ParseAndCacheRegionList(value->GetList(), true); - return; - } - - if (ParseAndCacheRegionList(value->GetList(), true)) { - VLOG(2) << "Got valid region list"; - // Set default device region and it'll be updated when received valid - // timezone info. - SetFallbackDeviceRegion(); - // Fetch timezones list to determine default region of this device. - api_request_.GetTimezonesForRegions(base::BindOnce( - &BraveVpnService::OnFetchTimezones, base::Unretained(this))); - return; - } - } - - // Don't update purchased state during the background fetching. - if (!background_fetch) { - VLOG(2) << "Got invalid region list"; - SetPurchasedState(GetCurrentEnvironment(), PurchasedState::FAILED); - } -} - -bool BraveVpnService::ParseAndCacheRegionList( - const base::Value::List& region_value, - bool save_to_prefs) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - - regions_ = ParseRegionList(region_value); - VLOG(2) << __func__ << " : has regionlist: " << !regions_.empty(); - - // If we can't get region list, we can't determine device region. - if (regions_.empty()) - return false; - - if (save_to_prefs) - SetRegionListToPrefs(); - return true; -} - -void BraveVpnService::OnFetchTimezones(const std::string& timezones_list, - bool success) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - - absl::optional value = base::JSONReader::Read(timezones_list); - if (success && value && value->is_list()) { - VLOG(2) << "Got valid timezones list"; - SetDeviceRegionWithTimezone(value->GetList()); - } else { - VLOG(2) << "Failed to get invalid timezones list"; - } - - // Can set as purchased state now regardless of timezone fetching result. - // We use default one picked from region list as a device region on failure. - SetPurchasedState(GetCurrentEnvironment(), PurchasedState::PURCHASED); -} - -void BraveVpnService::SetDeviceRegionWithTimezone( - const base::Value::List& timezones_value) { +void BraveVpnService::ToggleConnection() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - const std::string current_time_zone = GetCurrentTimeZone(); - if (current_time_zone.empty()) + if (!is_purchased_user()) { return; - - for (const auto& timezones : timezones_value) { - DCHECK(timezones.is_dict()); - if (!timezones.is_dict()) - continue; - - const std::string* region_name = timezones.GetDict().FindString("name"); - if (!region_name) - continue; - const auto* timezone_list_value = timezones.GetDict().FindList("timezones"); - if (!timezone_list_value) - continue; - - for (const auto& timezone : *timezone_list_value) { - DCHECK(timezone.is_string()); - if (!timezone.is_string()) - continue; - if (current_time_zone == timezone.GetString()) { - VLOG(2) << "Found default region: " << *region_name; - SetDeviceRegion(*region_name); - // Use device region as a default selected region. - if (GetSelectedRegion().empty()) { - SetSelectedRegion(*region_name); - } - return; - } - } } -} -void BraveVpnService::SetDeviceRegion(const std::string& name) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - local_prefs_->SetString(prefs::kBraveVPNDeviceRegion, name); -} - -void BraveVpnService::SetSelectedRegion(const std::string& name) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - local_prefs_->SetString(prefs::kBraveVPNSelectedRegion, name); -} - -std::string BraveVpnService::GetDeviceRegion() const { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - return local_prefs_->GetString(prefs::kBraveVPNDeviceRegion); -} - -std::string BraveVpnService::GetSelectedRegion() const { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - return local_prefs_->GetString(prefs::kBraveVPNSelectedRegion); + connection_api_->ToggleConnection(); } -void BraveVpnService::SetFallbackDeviceRegion() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - // Set first item in the region list as a |device_region_| as a fallback. - DCHECK(!regions_.empty()); - SetDeviceRegion(regions_[0].name); -} - -std::string BraveVpnService::GetCurrentTimeZone() { +void BraveVpnService::GetConnectionState(GetConnectionStateCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (!test_timezone_.empty()) - return test_timezone_; - - return GetTimeZoneName(); + const auto state = connection_api_->GetConnectionState(); + VLOG(2) << __func__ << " : " << state; + std::move(callback).Run(connection_api_->GetConnectionState()); } void BraveVpnService::GetAllRegions(GetAllRegionsCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); std::vector regions; - for (const auto& region : regions_) { + for (const auto& region : + connection_api_->GetRegionDataManager().GetRegions()) { regions.push_back(region.Clone()); } std::move(callback).Run(std::move(regions)); @@ -418,42 +226,16 @@ void BraveVpnService::GetAllRegions(GetAllRegionsCallback callback) { void BraveVpnService::GetSelectedRegion(GetSelectedRegionCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); VLOG(2) << __func__; - DCHECK(!regions_.empty()) - << "regions data must be prepared before panel asks."; - - auto region_name = GetSelectedRegion(); - if (region_name.empty()) { - // Gives device region if there is no cached selected region. - VLOG(2) << __func__ << " : give device region instead."; - region_name = GetDeviceRegion(); - } - DCHECK(!region_name.empty()); - std::move(callback).Run( - GetRegionPtrWithNameFromRegionList(region_name, regions_)); + + auto region_name = + connection_api_->GetRegionDataManager().GetSelectedRegion(); + std::move(callback).Run(GetRegionPtrWithNameFromRegionList( + region_name, connection_api_->GetRegionDataManager().GetRegions())); } void BraveVpnService::SetSelectedRegion(mojom::RegionPtr region_ptr) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - auto connection_state = GetConnectionState(); - if (connection_state == ConnectionState::DISCONNECTING || - connection_state == ConnectionState::CONNECTING) { - VLOG(2) << __func__ << ": Current state: " << connection_state - << " : prevent changing selected region while previous operation " - "is in-progress"; - // This is workaround to prevent UI changes seleted region. - for (const auto& obs : observers_) { - obs->OnSelectedRegionChanged( - GetRegionPtrWithNameFromRegionList(GetSelectedRegion(), regions_)); - } - return; - } - - VLOG(2) << __func__ << " : " << region_ptr->name_pretty; - SetSelectedRegion(region_ptr->name); - - // As new selected region is used, |connection_info_| for previous selected - // should be cleared. - GetBraveVPNConnectionAPI()->ResetConnectionInfo(); + connection_api_->SetSelectedRegion(region_ptr->name); } void BraveVpnService::GetProductUrls(GetProductUrlsCallback callback) { @@ -470,7 +252,7 @@ void BraveVpnService::CreateSupportTicket( auto internal_callback = base::BindOnce(&BraveVpnService::OnCreateSupportTicket, weak_ptr_factory_.GetWeakPtr(), std::move(callback)); - api_request_.CreateSupportTicket( + api_request_->CreateSupportTicket( std::move(internal_callback), email, subject, body, ::brave_vpn::GetSubscriberCredential(local_prefs_)); } @@ -481,17 +263,11 @@ void BraveVpnService::GetSupportData(GetSupportDataCallback callback) { std::string os_version = version_info::GetOSType(); std::move(callback).Run(brave_version, os_version, - GetBraveVPNConnectionAPI()->GetHostname(), - GetTimeZoneName()); + connection_api_->GetHostname(), GetTimeZoneName()); } void BraveVpnService::ResetConnectionState() { - GetBraveVPNConnectionAPI()->ResetConnectionState(); -} - -BraveVPNOSConnectionAPI* BraveVpnService::GetBraveVPNConnectionAPI() const { - DCHECK(connection_api_); - return connection_api_; + connection_api_->ResetConnectionState(); } // NOTE(bsclifton): Desktop uses API to create a ticket. @@ -503,21 +279,10 @@ void BraveVpnService::OnCreateSupportTicket( std::move(callback).Run(success, ticket); } -void BraveVpnService::OnPreferenceChanged(const std::string& pref_name) { - if (pref_name == prefs::kBraveVPNSelectedRegion) { - for (const auto& obs : observers_) { - obs->OnSelectedRegionChanged( - GetRegionPtrWithNameFromRegionList(GetSelectedRegion(), regions_)); - } - return; - } -} - void BraveVpnService::UpdatePurchasedStateForSessionExpired( const std::string& env) { // Double check that we don't set session expired state for fresh user. - LoadCachedRegionData(); - if (regions_.empty()) { + if (!connection_api_->GetRegionDataManager().IsRegionDataReady()) { VLOG(1) << __func__ << " : Treat it as not purchased state for fresh user."; SetPurchasedState(env, PurchasedState::NOT_PURCHASED); return; @@ -626,23 +391,24 @@ void BraveVpnService::LoadPurchasedState(const std::string& domain) { return; } - if (!purchased_state_.has_value()) - SetPurchasedState(requested_env, PurchasedState::LOADING); + SetPurchasedState(requested_env, PurchasedState::LOADING); if (HasValidSubscriberCredential(local_prefs_)) { #if BUILDFLAG(IS_ANDROID) SetPurchasedState(requested_env, PurchasedState::PURCHASED); #else // Need to wait more till region data is fetched. - if (regions_.empty()) { - VLOG(2) << __func__ << ": Wait till we get valid region data."; - SetPurchasedState(requested_env, PurchasedState::LOADING); - } else { + if (connection_api_->GetRegionDataManager().IsRegionDataReady()) { VLOG(2) << __func__ << ": Set as a purchased user as we have valid subscriber " "credentials & region data"; SetPurchasedState(requested_env, PurchasedState::PURCHASED); + } else { + VLOG(2) << __func__ << ": Wait till we get valid region data."; + // TODO(simonhong): Make purchases state independent from region data. + wait_region_data_ready_ = true; } + connection_api_->GetRegionDataManager().FetchRegionDataIfNeeded(); #endif return; } @@ -657,7 +423,7 @@ void BraveVpnService::LoadPurchasedState(const std::string& domain) { SetCurrentEnvironment(requested_env); } - api_request_.GetSubscriberCredentialV12( + api_request_->GetSubscriberCredentialV12( base::BindOnce(&BraveVpnService::OnGetSubscriberCredentialV12, base::Unretained(this), GetExpirationTimeForSkusCredential(local_prefs_)), @@ -795,7 +561,7 @@ void BraveVpnService::OnPrepareCredentialsPresentation( SetCurrentEnvironment(env); } - api_request_.GetSubscriberCredentialV12( + api_request_->GetSubscriberCredentialV12( base::BindOnce(&BraveVpnService::OnGetSubscriberCredentialV12, base::Unretained(this), time), credential, GetBraveVPNPaymentsEnv(GetCurrentEnvironment())); @@ -830,16 +596,13 @@ void BraveVpnService::OnGetSubscriberCredentialV12( #if BUILDFLAG(IS_ANDROID) SetPurchasedState(GetCurrentEnvironment(), PurchasedState::PURCHASED); #else - LoadCachedRegionData(); - - // Only fetch when we don't have cache. - if (!regions_.empty()) { + if (connection_api_->GetRegionDataManager().IsRegionDataReady()) { SetPurchasedState(GetCurrentEnvironment(), PurchasedState::PURCHASED); } else { - FetchRegionData(false); + wait_region_data_ready_ = true; } - ScheduleBackgroundRegionDataFetch(); + connection_api_->GetRegionDataManager().FetchRegionDataIfNeeded(); #endif } @@ -943,7 +706,7 @@ void BraveVpnService::SetPurchasedState(const std::string& env, #if !BUILDFLAG(IS_ANDROID) if (state == PurchasedState::PURCHASED) - GetBraveVPNConnectionAPI()->CheckConnection(); + connection_api_->CheckConnection(); #endif } @@ -972,7 +735,9 @@ void BraveVpnService::Shutdown() { skus_service_.reset(); observers_.Clear(); - + api_request_.reset(); + p3a_timer_.Stop(); + subs_cred_refresh_timer_.Stop(); #if !BUILDFLAG(IS_ANDROID) observed_.Reset(); receivers_.Clear(); @@ -980,24 +745,24 @@ void BraveVpnService::Shutdown() { } void BraveVpnService::GetAllServerRegions(ResponseCallback callback) { - api_request_.GetAllServerRegions(std::move(callback)); + api_request_->GetAllServerRegions(std::move(callback)); } void BraveVpnService::GetTimezonesForRegions(ResponseCallback callback) { - api_request_.GetTimezonesForRegions(std::move(callback)); + api_request_->GetTimezonesForRegions(std::move(callback)); } void BraveVpnService::GetHostnamesForRegion(ResponseCallback callback, const std::string& region) { - api_request_.GetHostnamesForRegion(std::move(callback), region); + api_request_->GetHostnamesForRegion(std::move(callback), region); } void BraveVpnService::GetProfileCredentials( ResponseCallback callback, const std::string& subscriber_credential, const std::string& hostname) { - api_request_.GetProfileCredentials(std::move(callback), subscriber_credential, - hostname); + api_request_->GetProfileCredentials(std::move(callback), + subscriber_credential, hostname); } void BraveVpnService::GetWireguardProfileCredentials( @@ -1005,7 +770,7 @@ void BraveVpnService::GetWireguardProfileCredentials( const std::string& subscriber_credential, const std::string& public_key, const std::string& hostname) { - api_request_.GetWireguardProfileCredentials( + api_request_->GetWireguardProfileCredentials( std::move(callback), subscriber_credential, public_key, hostname); } @@ -1015,8 +780,8 @@ void BraveVpnService::VerifyCredentials( const std::string& client_id, const std::string& subscriber_credential, const std::string& api_auth_token) { - api_request_.VerifyCredentials(std::move(callback), hostname, client_id, - subscriber_credential, api_auth_token); + api_request_->VerifyCredentials(std::move(callback), hostname, client_id, + subscriber_credential, api_auth_token); } void BraveVpnService::InvalidateCredentials( @@ -1025,8 +790,8 @@ void BraveVpnService::InvalidateCredentials( const std::string& client_id, const std::string& subscriber_credential, const std::string& api_auth_token) { - api_request_.InvalidateCredentials(std::move(callback), hostname, client_id, - subscriber_credential, api_auth_token); + api_request_->InvalidateCredentials(std::move(callback), hostname, client_id, + subscriber_credential, api_auth_token); } void BraveVpnService::VerifyPurchaseToken(ResponseCallback callback, @@ -1034,8 +799,8 @@ void BraveVpnService::VerifyPurchaseToken(ResponseCallback callback, const std::string& product_id, const std::string& product_type, const std::string& bundle_id) { - api_request_.VerifyPurchaseToken(std::move(callback), purchase_token, - product_id, product_type, bundle_id); + api_request_->VerifyPurchaseToken(std::move(callback), purchase_token, + product_id, product_type, bundle_id); } void BraveVpnService::GetSubscriberCredential( @@ -1045,9 +810,9 @@ void BraveVpnService::GetSubscriberCredential( const std::string& validation_method, const std::string& purchase_token, const std::string& bundle_id) { - api_request_.GetSubscriberCredential(std::move(callback), product_type, - product_id, validation_method, - purchase_token, bundle_id); + api_request_->GetSubscriberCredential(std::move(callback), product_type, + product_id, validation_method, + purchase_token, bundle_id); } void BraveVpnService::GetSubscriberCredentialV12(ResponseCallback callback) { diff --git a/components/brave_vpn/browser/brave_vpn_service.h b/components/brave_vpn/browser/brave_vpn_service.h index bc457923f0e5..c027fe94395e 100644 --- a/components/brave_vpn/browser/brave_vpn_service.h +++ b/components/brave_vpn/browser/brave_vpn_service.h @@ -164,26 +164,8 @@ class BraveVpnService : // BraveVPNOSConnectionAPI::Observer overrides: void OnConnectionStateChanged(mojom::ConnectionState state) override; - - void LoadCachedRegionData(); - void FetchRegionData(bool background_fetch); - void OnFetchRegionList(bool background_fetch, - const std::string& region_list, - bool success); - bool ParseAndCacheRegionList(const base::Value::List& region_value, - bool save_to_prefs = false); - void OnFetchTimezones(const std::string& timezones_list, bool success); - void SetDeviceRegionWithTimezone(const base::Value::List& timezons_value); - void SetDeviceRegion(const std::string& name); - void SetSelectedRegion(const std::string& name); - std::string GetDeviceRegion() const; - std::string GetSelectedRegion() const; - void SetFallbackDeviceRegion(); - void SetRegionListToPrefs(); - - std::string GetCurrentTimeZone(); - void ScheduleBackgroundRegionDataFetch(); - void ScheduleFetchRegionDataIfNeeded(); + void OnRegionDataReady(bool success) override; + void OnSelectedRegionChanged(const std::string& region_name) override; void OnCreateSupportTicket(CreateSupportTicketCallback callback, const std::string& ticket, @@ -192,8 +174,6 @@ class BraveVpnService : void OnPreferenceChanged(const std::string& pref_name); void UpdatePurchasedStateForSessionExpired(const std::string& env); - - BraveVPNOSConnectionAPI* GetBraveVPNConnectionAPI() const; #endif // !BUILDFLAG(IS_ANDROID) // KeyedService overrides: @@ -222,17 +202,13 @@ class BraveVpnService : void CheckInitialState(); #if !BUILDFLAG(IS_ANDROID) - std::vector regions_; base::ScopedObservation observed_{this}; - base::RepeatingTimer region_data_update_timer_; - - // Only for testing. - std::string test_timezone_; + bool wait_region_data_ready_ = false; raw_ptr connection_api_ = nullptr; - PrefChangeRegistrar pref_change_registrar_; + PrefChangeRegistrar policy_pref_change_registrar_; #endif // !BUILDFLAG(IS_ANDROID) SEQUENCE_CHECKER(sequence_checker_); @@ -245,7 +221,7 @@ class BraveVpnService : mojo::Remote skus_service_; absl::optional purchased_state_; mojo::RemoteSet observers_; - BraveVpnAPIRequest api_request_; + std::unique_ptr api_request_; base::RepeatingTimer p3a_timer_; base::OneShotTimer subs_cred_refresh_timer_; base::WeakPtrFactory weak_ptr_factory_{this}; diff --git a/components/brave_vpn/browser/brave_vpn_service_helper.cc b/components/brave_vpn/browser/brave_vpn_service_helper.cc index fad1c376cfa7..eb7fc087a9be 100644 --- a/components/brave_vpn/browser/brave_vpn_service_helper.cc +++ b/components/brave_vpn/browser/brave_vpn_service_helper.cc @@ -23,72 +23,6 @@ namespace brave_vpn { -namespace { - -bool IsValidRegionValue(const base::Value::Dict& value) { - if (!value.FindString(kRegionContinentKey) || - !value.FindString(kRegionNameKey) || - !value.FindString(kRegionNamePrettyKey) || - !value.FindString(kRegionCountryIsoCodeKey)) { - return false; - } - - return true; -} - -mojom::Region GetRegionFromValue(const base::Value::Dict& value) { - mojom::Region region; - if (auto* continent = value.FindString(brave_vpn::kRegionContinentKey)) - region.continent = *continent; - if (auto* name = value.FindString(brave_vpn::kRegionNameKey)) - region.name = *name; - if (auto* name_pretty = value.FindString(brave_vpn::kRegionNamePrettyKey)) - region.name_pretty = *name_pretty; - if (auto* country_iso_code = - value.FindString(brave_vpn::kRegionCountryIsoCodeKey)) - region.country_iso_code = *country_iso_code; - return region; -} - -} // namespace - -bool ValidateCachedRegionData(const base::Value::List& region_value) { - for (const auto& value : region_value) { - // Make sure cached one has all latest properties. - if (!value.is_dict() || !IsValidRegionValue(value.GetDict())) - return false; - } - - return true; -} - -base::Value::Dict GetValueFromRegion(const mojom::Region& region) { - base::Value::Dict region_dict; - region_dict.Set(kRegionContinentKey, region.continent); - region_dict.Set(kRegionNameKey, region.name); - region_dict.Set(kRegionNamePrettyKey, region.name_pretty); - region_dict.Set(kRegionCountryIsoCodeKey, region.country_iso_code); - return region_dict; -} - -std::vector ParseRegionList( - const base::Value::List& region_list) { - std::vector regions; - for (const auto& value : region_list) { - DCHECK(value.is_dict()); - if (!value.is_dict()) - continue; - regions.push_back(GetRegionFromValue(value.GetDict())); - } - - // Sort region list alphabetically - std::sort(regions.begin(), regions.end(), - [](mojom::Region& a, mojom::Region& b) { - return (a.name_pretty < b.name_pretty); - }); - return regions; -} - mojom::RegionPtr GetRegionPtrWithNameFromRegionList( const std::string& name, const std::vector region_list) { diff --git a/components/brave_vpn/browser/brave_vpn_service_helper.h b/components/brave_vpn/browser/brave_vpn_service_helper.h index def71958825e..5198e8704f86 100644 --- a/components/brave_vpn/browser/brave_vpn_service_helper.h +++ b/components/brave_vpn/browser/brave_vpn_service_helper.h @@ -22,13 +22,9 @@ class Value; } // namespace base namespace brave_vpn { -bool ValidateCachedRegionData(const base::Value::List& region_value); mojom::RegionPtr GetRegionPtrWithNameFromRegionList( const std::string& name, const std::vector region_list); -std::vector ParseRegionList( - const base::Value::List& region_list); -base::Value::Dict GetValueFromRegion(const mojom::Region& region); // False if subscription is expired. bool IsValidCredentialSummary(const base::Value& summary); diff --git a/components/brave_vpn/browser/brave_vpn_service_unittest.cc b/components/brave_vpn/browser/brave_vpn_service_unittest.cc index a6f420c0c721..b6cd69099837 100644 --- a/components/brave_vpn/browser/brave_vpn_service_unittest.cc +++ b/components/brave_vpn/browser/brave_vpn_service_unittest.cc @@ -16,6 +16,7 @@ #include "base/strings/string_number_conversions.h" #include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_feature_list.h" +#include "brave/components/brave_vpn/browser/api/brave_vpn_api_request.h" #include "brave/components/brave_vpn/browser/brave_vpn_service.h" #include "brave/components/brave_vpn/browser/brave_vpn_service_helper.h" #include "brave/components/brave_vpn/browser/connection/brave_vpn_connection_info.h" @@ -210,8 +211,10 @@ class BraveVPNServiceTest : public testing::Test { // Setup required for SKU (dependency of VPN) skus_service_ = std::make_unique( &local_pref_service_, url_loader_factory_.GetSafeWeakWrapper()); +#if !BUILDFLAG(IS_ANDROID) connection_api_ = std::make_unique( shared_url_loader_factory_, &local_pref_service_); +#endif ResetVpnService(); } @@ -219,6 +222,8 @@ class BraveVPNServiceTest : public testing::Test { if (service_) { service_->Shutdown(); } + skus_service_.reset(); + connection_api_.reset(); } void ResetVpnService() { @@ -231,6 +236,7 @@ class BraveVPNServiceTest : public testing::Test { base::BindRepeating(&BraveVPNServiceTest::GetSkusService, base::Unretained(this))); } + mojo::PendingRemote GetSkusService() { if (!skus_service_) { return mojo::PendingRemote(); @@ -238,9 +244,11 @@ class BraveVPNServiceTest : public testing::Test { return static_cast(skus_service_.get()) ->MakeRemote(); } + void SetInterceptorResponse(const std::string& response) { https_response_ = response; } + void Interceptor(const network::ResourceRequest& request) { url_loader_factory_.ClearResponses(); url_loader_factory_.AddResponse(request.url.spec(), https_response_); @@ -253,44 +261,54 @@ class BraveVPNServiceTest : public testing::Test { void SetPurchasedState(const std::string& env, PurchasedState state) { service_->SetPurchasedState(env, state); } + void LoadPurchasedState(const std::string& domain) { service_->LoadPurchasedState(domain); } + PurchasedState GetPurchasedStateSync() const { return service_->GetPurchasedStateSync(); } #if !BUILDFLAG(IS_ANDROID) + bool& wait_region_data_ready() { return service_->wait_region_data_ready_; } + mojom::Region device_region() const { - if (auto region_ptr = GetRegionPtrWithNameFromRegionList( - service_->GetDeviceRegion(), regions())) { + if (auto region_ptr = + GetRegionPtrWithNameFromRegionList(GetBraveVPNConnectionAPI() + ->GetRegionDataManager() + .GetDeviceRegion(), + regions())) { return *region_ptr; } return mojom::Region(); } + void OnCredentialSummary(const std::string& domain, const std::string& summary) { service_->OnCredentialSummary(domain, summary); } - std::vector& regions() const { return service_->regions_; } + const std::vector& regions() const { + return GetBraveVPNConnectionAPI()->GetRegionDataManager().GetRegions(); + } + void SetSelectedRegion(const std::string& region) { - service_->SetSelectedRegion(region); + GetBraveVPNConnectionAPI()->SetSelectedRegion(region); } - void OnFetchRegionList(bool background_fetch, - const std::string& region_list, - bool success) { - service_->OnFetchRegionList(background_fetch, region_list, success); + void OnFetchRegionList(const std::string& region_list, bool success) { + GetBraveVPNConnectionAPI()->GetRegionDataManager().OnFetchRegionList( + region_list, success); } void OnFetchTimezones(const std::string& timezones_list, bool success) { - service_->OnFetchTimezones(timezones_list, success); + GetBraveVPNConnectionAPI()->GetRegionDataManager().OnFetchTimezones( + timezones_list, success); } - void LoadCachedRegionData() { service_->LoadCachedRegionData(); } - BraveVPNOSConnectionAPI* GetBraveVPNConnectionAPI() { - return service_->GetBraveVPNConnectionAPI(); + BraveVPNOSConnectionAPIBase* GetBraveVPNConnectionAPI() const { + return static_cast(service_->connection_api_); } void OnGetSubscriberCredentialV12(const std::string& subscriber_credential, @@ -306,13 +324,18 @@ class BraveVPNServiceTest : public testing::Test { } void SetDeviceRegion(const std::string& name) { - service_->SetDeviceRegion(name); + GetBraveVPNConnectionAPI()->GetRegionDataManager().SetDeviceRegion(name); } - void SetFallbackDeviceRegion() { service_->SetFallbackDeviceRegion(); } + void SetFallbackDeviceRegion() { + GetBraveVPNConnectionAPI() + ->GetRegionDataManager() + .SetFallbackDeviceRegion(); + } void SetTestTimezone(const std::string& timezone) { - service_->test_timezone_ = timezone; + GetBraveVPNConnectionAPI()->GetRegionDataManager().test_timezone_ = + timezone; } BraveVPNOSConnectionAPI* GetConnectionAPI() { return connection_api_.get(); } @@ -476,9 +499,9 @@ class BraveVPNServiceTest : public testing::Test { sync_preferences::TestingPrefServiceSyncable profile_pref_service_; std::unique_ptr skus_service_; std::unique_ptr service_; + data_decoder::test::InProcessDataDecoder in_process_data_decoder_; network::TestURLLoaderFactory url_loader_factory_; scoped_refptr shared_url_loader_factory_; - data_decoder::test::InProcessDataDecoder in_process_data_decoder_; base::HistogramTester histogram_tester_; }; @@ -506,41 +529,6 @@ TEST_F(BraveVPNServiceTest, ResponseSanitizingTest) { } #if !BUILDFLAG(IS_ANDROID) -TEST_F(BraveVPNServiceTest, RegionDataTest) { - // Test invalid region data. - OnFetchRegionList(false, std::string(), true); - EXPECT_TRUE(regions().empty()); - - // Test valid region data parsing. - OnFetchRegionList(false, GetRegionsData(), true); - const size_t kRegionCount = 11; - EXPECT_EQ(kRegionCount, regions().size()); - - // First region in region list is set as a device region when fetch is failed. - OnFetchTimezones(std::string(), false); - EXPECT_EQ(regions()[0], device_region()); - - // Test fallback region is replaced with proper device region - // when valid timezone is used. - // "asia-sg" region is used for "Asia/Seoul" tz. - SetFallbackDeviceRegion(); - SetTestTimezone("Asia/Seoul"); - OnFetchTimezones(GetTimeZonesData(), true); - EXPECT_EQ("asia-sg", device_region().name); - - // Test device region is not changed when invalid timezone is set. - SetFallbackDeviceRegion(); - SetTestTimezone("Invalid"); - OnFetchTimezones(GetTimeZonesData(), true); - EXPECT_EQ(regions()[0], device_region()); - - // Test device region is not changed when invalid timezone is set. - SetFallbackDeviceRegion(); - SetTestTimezone("Invalid"); - OnFetchTimezones(GetTimeZonesData(), true); - EXPECT_EQ(regions()[0], device_region()); -} - TEST_F(BraveVPNServiceTest, SkusCredentialCacheTest) { std::string env = skus::GetDefaultEnvironment(); std::string domain = skus::GetDomain("vpn", env); @@ -587,7 +575,7 @@ TEST_F(BraveVPNServiceTest, LoadPurchasedStateSessionExpiredTest) { EXPECT_TRUE(session_expired_time.is_null()); // Session expired state for non-fresh user. - OnFetchRegionList(false, GetRegionsData(), true); + OnFetchRegionList(GetRegionsData(), true); OnCredentialSummary( domain, R"({ "active": false, "remaining_credential_count": 0 } )"); EXPECT_EQ(PurchasedState::SESSION_EXPIRED, GetPurchasedStateSync()); @@ -636,6 +624,8 @@ TEST_F(BraveVPNServiceTest, LoadPurchasedStateTest) { // Reached to purchased state when valid credential, region data // and timezone info. SetPurchasedState(env, PurchasedState::LOADING); + // We can set purchased state after getting region data. + wait_region_data_ready() = true; OnCredentialSummary( domain, R"({ "active": true, "remaining_credential_count": 1 } )"); EXPECT_TRUE(regions().empty()); @@ -643,15 +633,18 @@ TEST_F(BraveVPNServiceTest, LoadPurchasedStateTest) { OnPrepareCredentialsPresentation( domain, "credential=abcdefghijk; Expires=Wed, 21 Oct 2050 07:28:00 GMT"); EXPECT_EQ(PurchasedState::LOADING, GetPurchasedStateSync()); - OnFetchRegionList(false, GetRegionsData(), true); + OnFetchRegionList(GetRegionsData(), true); EXPECT_EQ(PurchasedState::LOADING, GetPurchasedStateSync()); SetTestTimezone("Asia/Seoul"); OnFetchTimezones(GetTimeZonesData(), true); + EXPECT_FALSE(wait_region_data_ready()); EXPECT_EQ(PurchasedState::PURCHASED, GetPurchasedStateSync()); // Check purchased is set when fetching timezone is failed. SetPurchasedState(env, PurchasedState::LOADING); + wait_region_data_ready() = true; OnFetchTimezones("", false); + EXPECT_FALSE(wait_region_data_ready()); EXPECT_EQ(PurchasedState::PURCHASED, GetPurchasedStateSync()); // Treat not purchased when empty. @@ -713,7 +706,7 @@ TEST_F(BraveVPNServiceTest, SelectedRegionChangedUpdateTest) { TestBraveVPNServiceObserver observer; AddObserver(observer.GetReceiver()); - OnFetchRegionList(false, GetRegionsData(), true); + OnFetchRegionList(GetRegionsData(), true); SetSelectedRegion("asia-sg"); base::RunLoop loop; observer.WaitSelectedRegionStateChange(loop.QuitClosure()); @@ -726,7 +719,7 @@ TEST_F(BraveVPNServiceTest, SelectedRegionChangedUpdateWithDeviceRegionTest) { TestBraveVPNServiceObserver observer; AddObserver(observer.GetReceiver()); - OnFetchRegionList(false, GetRegionsData(), true); + OnFetchRegionList(GetRegionsData(), true); SetTestTimezone("Asia/Seoul"); OnFetchTimezones(GetTimeZonesData(), true); base::RunLoop loop; @@ -734,31 +727,6 @@ TEST_F(BraveVPNServiceTest, SelectedRegionChangedUpdateWithDeviceRegionTest) { loop.Run(); } -TEST_F(BraveVPNServiceTest, LoadRegionDataFromPrefsTest) { - std::string env = skus::GetDefaultEnvironment(); - // Initially, prefs doesn't have region data. - EXPECT_EQ(mojom::Region(), device_region()); - EXPECT_TRUE(regions().empty()); - - // Set proper data to store them in prefs. - OnFetchRegionList(false, GetRegionsData(), true); - SetTestTimezone("Asia/Seoul"); - OnFetchTimezones(GetTimeZonesData(), true); - - // Check region data is set with above data. - EXPECT_FALSE(mojom::Region() == device_region()); - EXPECT_FALSE(regions().empty()); - - // Clear region data. - regions().clear(); - EXPECT_TRUE(regions().empty()); - - // Check region data is loaded from prefs. - SetPurchasedState(env, PurchasedState::LOADING); - LoadCachedRegionData(); - EXPECT_FALSE(regions().empty()); -} - TEST_F(BraveVPNServiceTest, LoadPurchasedStateForAnotherEnvFailed) { auto development = SetupTestingStoreForEnv(skus::GetDefaultEnvironment()); EXPECT_EQ(skus::GetEnvironmentForDomain(development), @@ -833,6 +801,7 @@ TEST_F(BraveVPNServiceTest, CheckInitialPurchasedStateTest) { // Set valid subscriber credential to pretend it's purchased user. SetValidSubscriberCredential(); ResetVpnService(); + // Loading state if region data is not ready. EXPECT_EQ(PurchasedState::LOADING, GetPurchasedStateSync()); // Set in-valid subscriber credential but not empty to pretend it's purchased @@ -916,7 +885,7 @@ TEST_F(BraveVPNServiceTest, LoadPurchasedStateNotifications) { LoadPurchasedState(domain); { - // Loading state called if we fetch it first time. + // Loading state called if we load purchased state. base::RunLoop loop; observer.WaitPurchasedStateChange(loop.QuitClosure()); loop.Run(); @@ -937,10 +906,20 @@ TEST_F(BraveVPNServiceTest, LoadPurchasedStateNotifications) { EXPECT_FALSE(observer.GetPurchasedState().has_value()); EXPECT_EQ(PurchasedState::NOT_PURCHASED, GetPurchasedStateSync()); LoadPurchasedState(domain); + { + // Loading state called whenever we load purchased state. + base::RunLoop loop; + observer.WaitPurchasedStateChange(loop.QuitClosure()); + loop.Run(); + EXPECT_EQ(PurchasedState::LOADING, GetPurchasedStateSync()); + EXPECT_TRUE(observer.GetPurchasedState().has_value()); + EXPECT_EQ(PurchasedState::LOADING, observer.GetPurchasedState().value()); + } base::RunLoop().RunUntilIdle(); - // Observer event not called second time because the state is not changed. - EXPECT_FALSE(observer.GetPurchasedState().has_value()); + EXPECT_TRUE(observer.GetPurchasedState().has_value()); + EXPECT_EQ(PurchasedState::NOT_PURCHASED, GetPurchasedStateSync()); // Observer called when state will be changed. + SetAndExpectPurchasedStateChange(&observer, env, PurchasedState::LOADING); SetAndExpectPurchasedStateChange(&observer, env, PurchasedState::PURCHASED); } diff --git a/components/brave_vpn/browser/connection/BUILD.gn b/components/brave_vpn/browser/connection/BUILD.gn index e6d5e26f7318..13a4d8386be9 100644 --- a/components/brave_vpn/browser/connection/BUILD.gn +++ b/components/brave_vpn/browser/connection/BUILD.gn @@ -40,6 +40,8 @@ source_set("common") { "brave_vpn_connection_info.h", "brave_vpn_os_connection_api_base.cc", "brave_vpn_os_connection_api_base.h", + "brave_vpn_region_data_manager.cc", + "brave_vpn_region_data_manager.h", ] deps = [ ":api", @@ -80,7 +82,9 @@ source_set("unit_tests") { deps = [ ":api", + ":common", ":sim", + "//brave/components/brave_vpn/browser", "//brave/components/brave_vpn/common", "//brave/components/brave_vpn/common/mojom", "//components/sync_preferences:test_support", diff --git a/components/brave_vpn/browser/connection/brave_vpn_os_connection_api.h b/components/brave_vpn/browser/connection/brave_vpn_os_connection_api.h index d958004b5ad7..718fee5a46d7 100644 --- a/components/brave_vpn/browser/connection/brave_vpn_os_connection_api.h +++ b/components/brave_vpn/browser/connection/brave_vpn_os_connection_api.h @@ -24,6 +24,8 @@ class PrefService; namespace brave_vpn { +class BraveVPNRegionDataManager; + // Interface for managing OS' vpn connection. class BraveVPNOSConnectionAPI { public: @@ -32,6 +34,9 @@ class BraveVPNOSConnectionAPI { class Observer : public base::CheckedObserver { public: virtual void OnConnectionStateChanged(mojom::ConnectionState state) = 0; + // false when fetching region data is failed. + virtual void OnRegionDataReady(bool success) = 0; + virtual void OnSelectedRegionChanged(const std::string& region_name) = 0; protected: ~Observer() override = default; @@ -52,6 +57,8 @@ class BraveVPNOSConnectionAPI { // Returns user friendly error string if existed. // Otherwise returns empty. virtual std::string GetLastConnectionError() const = 0; + virtual BraveVPNRegionDataManager& GetRegionDataManager() = 0; + virtual void SetSelectedRegion(const std::string& name) = 0; protected: BraveVPNOSConnectionAPI() = default; diff --git a/components/brave_vpn/browser/connection/brave_vpn_os_connection_api_base.cc b/components/brave_vpn/browser/connection/brave_vpn_os_connection_api_base.cc index 3d4cd176584d..5802f0455998 100644 --- a/components/brave_vpn/browser/connection/brave_vpn_os_connection_api_base.cc +++ b/components/brave_vpn/browser/connection/brave_vpn_os_connection_api_base.cc @@ -30,8 +30,18 @@ BraveVPNOSConnectionAPIBase::BraveVPNOSConnectionAPIBase( version_info::Channel channel) : target_vpn_entry_name_(GetBraveVPNEntryName(channel)), local_prefs_(local_prefs), - url_loader_factory_(url_loader_factory) { - DCHECK(url_loader_factory && local_prefs); + url_loader_factory_(url_loader_factory), + region_data_manager_(url_loader_factory_, local_prefs_) { + DCHECK(url_loader_factory_ && local_prefs_); + + // Safe to use Unretained here because |region_data_manager_| is owned + // instance. + region_data_manager_.set_selected_region_changed_callback(base::BindRepeating( + &BraveVPNOSConnectionAPIBase::NotifySelectedRegionChanged, + base::Unretained(this))); + region_data_manager_.set_region_data_ready_callback( + base::BindRepeating(&BraveVPNOSConnectionAPIBase::NotifyRegionDataReady, + base::Unretained(this))); net::NetworkChangeNotifier::AddNetworkChangeObserver(this); } @@ -66,6 +76,33 @@ std::string BraveVPNOSConnectionAPIBase::GetLastConnectionError() const { return last_connection_error_; } +BraveVPNRegionDataManager& BraveVPNOSConnectionAPIBase::GetRegionDataManager() { + return region_data_manager_; +} + +void BraveVPNOSConnectionAPIBase::SetSelectedRegion(const std::string& name) { + // TODO(simonhong): Can remove this when UI block region changes while + // operation is in-progress. + // Don't allow region change while operation is in-progress. + auto connection_state = GetConnectionState(); + if (connection_state == ConnectionState::DISCONNECTING || + connection_state == ConnectionState::CONNECTING) { + VLOG(2) << __func__ << ": Current state: " << connection_state + << " : prevent changing selected region while previous operation " + "is in-progress"; + // This is workaround to prevent UI changes seleted region. + // Early return by notify again with current region name. + NotifySelectedRegionChanged(region_data_manager_.GetSelectedRegion()); + return; + } + + region_data_manager_.SetSelectedRegion(name); + + // As new selected region is used, |connection_info_| for previous selected + // should be cleared. + ResetConnectionInfo(); +} + bool BraveVPNOSConnectionAPIBase::IsInProgress() const { return connection_state_ == ConnectionState::DISCONNECTING || connection_state_ == ConnectionState::CONNECTING; @@ -121,9 +158,9 @@ void BraveVPNOSConnectionAPIBase::Connect() { } // If user doesn't select region explicitely, use default device region. - std::string target_region_name = GetSelectedRegion(); + std::string target_region_name = region_data_manager_.GetSelectedRegion(); if (target_region_name.empty()) { - target_region_name = GetDeviceRegion(); + target_region_name = region_data_manager_.GetDeviceRegion(); VLOG(2) << __func__ << " : start connecting with valid default_region: " << target_region_name; } @@ -324,14 +361,6 @@ BraveVpnAPIRequest* BraveVPNOSConnectionAPIBase::GetAPIRequest() { return api_request_.get(); } -std::string BraveVPNOSConnectionAPIBase::GetDeviceRegion() const { - return local_prefs_->GetString(prefs::kBraveVPNDeviceRegion); -} - -std::string BraveVPNOSConnectionAPIBase::GetSelectedRegion() const { - return local_prefs_->GetString(prefs::kBraveVPNSelectedRegion); -} - std::string BraveVPNOSConnectionAPIBase::GetCurrentEnvironment() const { return local_prefs_->GetString(prefs::kBraveVPNEnvironment); } @@ -466,4 +495,17 @@ mojom::ConnectionState BraveVPNOSConnectionAPIBase::GetConnectionState() const { return connection_state_; } +void BraveVPNOSConnectionAPIBase::NotifyRegionDataReady(bool ready) const { + for (auto& obs : observers_) { + obs.OnRegionDataReady(ready); + } +} + +void BraveVPNOSConnectionAPIBase::NotifySelectedRegionChanged( + const std::string& name) const { + for (auto& obs : observers_) { + obs.OnSelectedRegionChanged(name); + } +} + } // namespace brave_vpn diff --git a/components/brave_vpn/browser/connection/brave_vpn_os_connection_api_base.h b/components/brave_vpn/browser/connection/brave_vpn_os_connection_api_base.h index 7bb55e45b35f..bf2b6bee19d4 100644 --- a/components/brave_vpn/browser/connection/brave_vpn_os_connection_api_base.h +++ b/components/brave_vpn/browser/connection/brave_vpn_os_connection_api_base.h @@ -17,10 +17,10 @@ #include "brave/components/brave_vpn/browser/api/brave_vpn_api_request.h" #include "brave/components/brave_vpn/browser/connection/brave_vpn_connection_info.h" #include "brave/components/brave_vpn/browser/connection/brave_vpn_os_connection_api.h" +#include "brave/components/brave_vpn/browser/connection/brave_vpn_region_data_manager.h" #include "brave/components/brave_vpn/common/mojom/brave_vpn.mojom.h" #include "net/base/network_change_notifier.h" #include "services/network/public/cpp/shared_url_loader_factory.h" -#include "url/gurl.h" class PrefService; @@ -54,6 +54,8 @@ class BraveVPNOSConnectionAPIBase void RemoveObserver(Observer* observer) override; void SetConnectionState(mojom::ConnectionState state) override; std::string GetLastConnectionError() const override; + BraveVPNRegionDataManager& GetRegionDataManager() override; + void SetSelectedRegion(const std::string& name) override; protected: BraveVPNOSConnectionAPIBase( @@ -82,8 +84,10 @@ class BraveVPNOSConnectionAPIBase std::string target_vpn_entry_name() const { return target_vpn_entry_name_; } private: + friend class BraveVPNRegionDataManager; friend class BraveVPNOSConnectionAPISim; friend class BraveVPNOSConnectionAPIUnitTest; + friend class BraveVPNServiceTest; FRIEND_TEST_ALL_PREFIXES(BraveVPNOSConnectionAPIUnitTest, CreateOSVPNEntryWithValidInfoWhenConnectTest); @@ -106,8 +110,6 @@ class BraveVPNOSConnectionAPIBase net::NetworkChangeNotifier::ConnectionType type) override; void CreateVPNConnection(); - std::string GetSelectedRegion() const; - std::string GetDeviceRegion() const; std::string GetCurrentEnvironment() const; void FetchHostnamesForRegion(const std::string& name); void OnFetchHostnames(const std::string& region, @@ -124,6 +126,10 @@ class BraveVPNOSConnectionAPIBase bool QuickCancelIfPossible(); void SetPreventCreationForTesting(bool value); + // Notify it's ready when |regions_| is not empty. + void NotifyRegionDataReady(bool ready) const; + void NotifySelectedRegionChanged(const std::string& name) const; + bool cancel_connecting_ = false; bool needs_connect_ = false; bool prevent_creation_ = false; @@ -135,13 +141,16 @@ class BraveVPNOSConnectionAPIBase raw_ptr local_prefs_ = nullptr; std::unique_ptr hostname_; base::ObserverList observers_; + // Only not null when there is active network request. // When network request is done, we reset this so we can know // whether we're waiting the response or not. // We can cancel connecting request quickly when fetching hostnames or // profile credentials is not yet finished by reset this. std::unique_ptr api_request_; + scoped_refptr url_loader_factory_; + BraveVPNRegionDataManager region_data_manager_; }; } // namespace brave_vpn diff --git a/components/brave_vpn/browser/connection/brave_vpn_os_connection_api_unittest.cc b/components/brave_vpn/browser/connection/brave_vpn_os_connection_api_unittest.cc index 1dc0ead4a292..d7963eeb3f0e 100644 --- a/components/brave_vpn/browser/connection/brave_vpn_os_connection_api_unittest.cc +++ b/components/brave_vpn/browser/connection/brave_vpn_os_connection_api_unittest.cc @@ -7,10 +7,13 @@ #include #include +#include #include "base/memory/scoped_refptr.h" #include "base/run_loop.h" +#include "brave/components/brave_vpn/browser/brave_vpn_service_helper.h" #include "brave/components/brave_vpn/browser/connection/brave_vpn_os_connection_api_sim.h" +#include "brave/components/brave_vpn/browser/connection/brave_vpn_region_data_manager.h" #include "brave/components/brave_vpn/common/brave_vpn_data_types.h" #include "brave/components/brave_vpn/common/brave_vpn_utils.h" #include "brave/components/brave_vpn/common/mojom/brave_vpn.mojom.h" @@ -76,16 +79,266 @@ class BraveVPNOSConnectionAPIUnitTest : public testing::Test { &url_loader_factory_), local_state()); } + + void OnFetchRegionList(const std::string& region_list, bool success) { + GetBraveVPNConnectionAPIBase()->GetRegionDataManager().OnFetchRegionList( + region_list, success); + } + + void OnFetchTimezones(const std::string& timezones_list, bool success) { + GetBraveVPNConnectionAPIBase()->GetRegionDataManager().OnFetchTimezones( + timezones_list, success); + } + + std::string GetTimeZonesData() { + return R"([ + { + "name": "us-central", + "timezones": [ + "America/Guatemala", + "America/Guayaquil", + "America/Guyana", + "America/Havana" + ] + }, + { + "name": "eu-es", + "timezones": [ + "Europe/Madrid", + "Europe/Gibraltar", + "Africa/Casablanca", + "Africa/Algiers" + ] + }, + { + "name": "eu-ch", + "timezones": [ + "Europe/Zurich" + ] + }, + { + "name": "eu-nl", + "timezones": [ + "Europe/Amsterdam", + "Europe/Brussels" + ] + }, + { + "name": "asia-sg", + "timezones": [ + "Asia/Aden", + "Asia/Almaty", + "Asia/Seoul" + ] + }, + { + "name": "asia-jp", + "timezones": [ + "Pacific/Guam", + "Pacific/Saipan", + "Asia/Tokyo" + ] + } + ])"; + } + + std::string GetRegionsData() { + // Give 11 region data. + return R"([ + { + "continent": "europe", + "name": "eu-es", + "name-pretty": "Spain" + }, + { + "continent": "south-america", + "name": "sa-br", + "name-pretty": "Brazil" + }, + { + "continent": "europe", + "name": "eu-ch", + "name-pretty": "Switzerland" + }, + { + "continent": "europe", + "name": "eu-de", + "name-pretty": "Germany" + }, + { + "continent": "asia", + "name": "asia-sg", + "name-pretty": "Singapore" + }, + { + "continent": "north-america", + "name": "ca-east", + "name-pretty": "Canada" + }, + { + "continent": "asia", + "name": "asia-jp", + "name-pretty": "Japan" + }, + { + "continent": "europe", + "name": "eu-en", + "name-pretty": "United Kingdom" + }, + { + "continent": "europe", + "name": "eu-nl", + "name-pretty": "Netherlands" + }, + { + "continent": "north-america", + "name": "us-west", + "name-pretty": "USA West" + }, + { + "continent": "oceania", + "name": "au-au", + "name-pretty": "Australia" + } + ])"; + } + + BraveVPNOSConnectionAPIBase* GetBraveVPNConnectionAPIBase() const { + return static_cast(connection_api_.get()); + } + + void SetFallbackDeviceRegion() { + GetBraveVPNConnectionAPIBase() + ->GetRegionDataManager() + .SetFallbackDeviceRegion(); + } + + void SetTestTimezone(const std::string& timezone) { + GetBraveVPNConnectionAPIBase()->GetRegionDataManager().test_timezone_ = + timezone; + } + + void LoadCachedRegionData() { + GetBraveVPNConnectionAPIBase() + ->GetRegionDataManager() + .LoadCachedRegionData(); + } + + void ClearRegions() { + GetBraveVPNConnectionAPIBase()->GetRegionDataManager().regions_.clear(); + } + + bool NeedToUpdateRegionData() { + return GetBraveVPNConnectionAPIBase() + ->GetRegionDataManager() + .NeedToUpdateRegionData(); + } + + mojom::Region device_region() { + if (auto region_ptr = + GetRegionPtrWithNameFromRegionList(GetBraveVPNConnectionAPIBase() + ->GetRegionDataManager() + .GetDeviceRegion(), + regions())) { + return *region_ptr; + } + return mojom::Region(); + } + + const std::vector& regions() { + return GetBraveVPNConnectionAPIBase()->GetRegionDataManager().GetRegions(); + } + PrefService* local_state() { return &local_pref_service_; } + BraveVPNOSConnectionAPI* GetConnectionAPI() { return connection_api_.get(); } - private: + protected: TestingPrefServiceSimple local_pref_service_; network::TestURLLoaderFactory url_loader_factory_; content::BrowserTaskEnvironment task_environment_; std::unique_ptr connection_api_; }; +TEST_F(BraveVPNOSConnectionAPIUnitTest, LoadRegionDataFromPrefsTest) { + // Initially, prefs doesn't have region data. + EXPECT_EQ(mojom::Region(), device_region()); + EXPECT_TRUE(regions().empty()); + + // Set proper data to store them in prefs. + OnFetchRegionList(GetRegionsData(), true); + SetTestTimezone("Asia/Seoul"); + OnFetchTimezones(GetTimeZonesData(), true); + + // Check region data is set with above data. + EXPECT_FALSE(mojom::Region() == device_region()); + EXPECT_FALSE(regions().empty()); + + // Clear region data from api instance. + ClearRegions(); + EXPECT_TRUE(regions().empty()); + + // Check region data is loaded from prefs. + LoadCachedRegionData(); + EXPECT_FALSE(regions().empty()); +} + +TEST_F(BraveVPNOSConnectionAPIUnitTest, RegionDataTest) { + // Initially, prefs doesn't have region data. + EXPECT_EQ(mojom::Region(), device_region()); + EXPECT_TRUE(regions().empty()); + + // Test invalid region data. + OnFetchRegionList(std::string(), true); + EXPECT_TRUE(regions().empty()); + + // Test valid region data parsing. + OnFetchRegionList(GetRegionsData(), true); + const size_t kRegionCount = 11; + EXPECT_EQ(kRegionCount, regions().size()); + + // First region in region list is set as a device region when fetch is failed. + OnFetchTimezones(std::string(), false); + EXPECT_EQ(regions()[0], device_region()); + + // Test fallback region is replaced with proper device region + // when valid timezone is used. + // "asia-sg" region is used for "Asia/Seoul" tz. + SetFallbackDeviceRegion(); + SetTestTimezone("Asia/Seoul"); + OnFetchTimezones(GetTimeZonesData(), true); + EXPECT_EQ("asia-sg", device_region().name); + + // Test device region is not changed when invalid timezone is set. + SetFallbackDeviceRegion(); + SetTestTimezone("Invalid"); + OnFetchTimezones(GetTimeZonesData(), true); + EXPECT_EQ(regions()[0], device_region()); + + // Test device region is not changed when invalid timezone is set. + SetFallbackDeviceRegion(); + SetTestTimezone("Invalid"); + OnFetchTimezones(GetTimeZonesData(), true); + EXPECT_EQ(regions()[0], device_region()); +} + +TEST_F(BraveVPNOSConnectionAPIUnitTest, NeedToUpdateRegionDataTest) { + // Initially, need to update region data. + EXPECT_TRUE(NeedToUpdateRegionData()); + + // Still need to update. + OnFetchRegionList(std::string(), true); + EXPECT_TRUE(NeedToUpdateRegionData()); + + // Don't need to update when got valid region data. + OnFetchRegionList(GetRegionsData(), true); + EXPECT_FALSE(NeedToUpdateRegionData()); + + // Need to update again after 5h passed. + task_environment_.AdvanceClock(base::Hours(5)); + EXPECT_TRUE(NeedToUpdateRegionData()); +} + // Create os vpn entry with cached connection_info when there is cached // connection info. TEST_F(BraveVPNOSConnectionAPIUnitTest, @@ -107,6 +360,9 @@ TEST_F(BraveVPNOSConnectionAPIUnitTest, } TEST_F(BraveVPNOSConnectionAPIUnitTest, CreateOSVPNEntryWithInvalidInfoTest) { + // Prepare region data before asking connect. + OnFetchRegionList(GetRegionsData(), true); + GetConnectionAPI()->CheckConnection(); local_state()->SetString(prefs::kBraveVPNSelectedRegion, "region-a"); // Prepare valid connection info. @@ -124,6 +380,9 @@ TEST_F(BraveVPNOSConnectionAPIUnitTest, CreateOSVPNEntryWithInvalidInfoTest) { } TEST_F(BraveVPNOSConnectionAPIUnitTest, NeedsConnectTest) { + // Prepare region data before asking connect. + OnFetchRegionList(GetRegionsData(), true); + auto* test_api = static_cast(GetConnectionAPI()); diff --git a/components/brave_vpn/browser/connection/brave_vpn_region_data_manager.cc b/components/brave_vpn/browser/connection/brave_vpn_region_data_manager.cc new file mode 100644 index 000000000000..1f7281b2e282 --- /dev/null +++ b/components/brave_vpn/browser/connection/brave_vpn_region_data_manager.cc @@ -0,0 +1,356 @@ +/* Copyright (c) 2023 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 https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/brave_vpn/browser/connection/brave_vpn_region_data_manager.h" + +#include +#include + +#include "base/check_is_test.h" +#include "base/functional/bind.h" +#include "base/functional/callback_forward.h" +#include "base/json/json_reader.h" +#include "brave/components/brave_vpn/browser/api/brave_vpn_api_helper.h" +#include "brave/components/brave_vpn/common/brave_vpn_constants.h" +#include "brave/components/brave_vpn/common/pref_names.h" +#include "components/prefs/pref_service.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace brave_vpn { + +namespace { + +base::Value::Dict GetValueFromRegion(const mojom::Region& region) { + base::Value::Dict region_dict; + region_dict.Set(kRegionContinentKey, region.continent); + region_dict.Set(kRegionNameKey, region.name); + region_dict.Set(kRegionNamePrettyKey, region.name_pretty); + region_dict.Set(kRegionCountryIsoCodeKey, region.country_iso_code); + return region_dict; +} + +bool IsValidRegionValue(const base::Value::Dict& value) { + if (!value.FindString(kRegionContinentKey) || + !value.FindString(kRegionNameKey) || + !value.FindString(kRegionNamePrettyKey) || + !value.FindString(kRegionCountryIsoCodeKey)) { + return false; + } + + return true; +} + +mojom::Region GetRegionFromValue(const base::Value::Dict& value) { + mojom::Region region; + if (auto* continent = value.FindString(brave_vpn::kRegionContinentKey)) { + region.continent = *continent; + } + if (auto* name = value.FindString(brave_vpn::kRegionNameKey)) { + region.name = *name; + } + if (auto* name_pretty = value.FindString(brave_vpn::kRegionNamePrettyKey)) { + region.name_pretty = *name_pretty; + } + if (auto* country_iso_code = + value.FindString(brave_vpn::kRegionCountryIsoCodeKey)) { + region.country_iso_code = *country_iso_code; + } + return region; +} + +bool ValidateCachedRegionData(const base::Value::List& region_value) { + for (const auto& value : region_value) { + // Make sure cached one has all latest properties. + if (!value.is_dict() || !IsValidRegionValue(value.GetDict())) { + return false; + } + } + + return true; +} + +std::vector ParseRegionList( + const base::Value::List& region_list) { + std::vector regions; + for (const auto& value : region_list) { + DCHECK(value.is_dict()); + if (!value.is_dict()) { + continue; + } + regions.push_back(GetRegionFromValue(value.GetDict())); + } + + // Sort region list alphabetically + std::sort(regions.begin(), regions.end(), + [](mojom::Region& a, mojom::Region& b) { + return (a.name_pretty < b.name_pretty); + }); + return regions; +} + +} // namespace + +BraveVPNRegionDataManager::BraveVPNRegionDataManager( + scoped_refptr url_loader_factory, + PrefService* local_prefs) + : url_loader_factory_(url_loader_factory), local_prefs_(local_prefs) { + LoadCachedRegionData(); +} + +BraveVPNRegionDataManager::~BraveVPNRegionDataManager() = default; + +const std::vector& BraveVPNRegionDataManager::GetRegions() + const { + return regions_; +} + +bool BraveVPNRegionDataManager::IsRegionDataReady() const { + return !regions_.empty(); +} + +void BraveVPNRegionDataManager::SetSelectedRegion(const std::string& name) { + local_prefs_->SetString(prefs::kBraveVPNSelectedRegion, name); + + if (selected_region_changed_callback_) { + selected_region_changed_callback_.Run(name); + } +} + +std::string BraveVPNRegionDataManager::GetSelectedRegion() const { + DCHECK(!regions_.empty()) + << "regions data must be prepared before panel asks."; + + auto region_name = local_prefs_->GetString(prefs::kBraveVPNSelectedRegion); + if (region_name.empty()) { + // Gives device region if there is no cached selected region. + VLOG(2) << __func__ << " : give device region instead."; + region_name = GetDeviceRegion(); + } + + DCHECK(!region_name.empty()); + return region_name; +} + +std::string BraveVPNRegionDataManager::GetDeviceRegion() const { + return local_prefs_->GetString(prefs::kBraveVPNDeviceRegion); +} + +void BraveVPNRegionDataManager::SetDeviceRegion(const std::string& name) { + local_prefs_->SetString(prefs::kBraveVPNDeviceRegion, name); +} + +void BraveVPNRegionDataManager::SetFallbackDeviceRegion() { + // Set first item in the region list as a |device_region_| as a fallback. + DCHECK(!regions_.empty()); + SetDeviceRegion(regions_[0].name); +} + +void BraveVPNRegionDataManager::SetDeviceRegionWithTimezone( + const base::Value::List& timezones_value) { + const std::string current_time_zone = GetCurrentTimeZone(); + if (current_time_zone.empty()) { + return; + } + + for (const auto& timezones : timezones_value) { + DCHECK(timezones.is_dict()); + if (!timezones.is_dict()) { + continue; + } + + const std::string* region_name = timezones.GetDict().FindString("name"); + if (!region_name) { + continue; + } + const auto* timezone_list_value = timezones.GetDict().FindList("timezones"); + if (!timezone_list_value) { + continue; + } + + for (const auto& timezone : *timezone_list_value) { + DCHECK(timezone.is_string()); + if (!timezone.is_string()) { + continue; + } + if (current_time_zone == timezone.GetString()) { + VLOG(2) << "Found default region: " << *region_name; + SetDeviceRegion(*region_name); + // Use device region as a default selected region. + if (local_prefs_->GetString(prefs::kBraveVPNSelectedRegion).empty()) { + SetSelectedRegion(*region_name); + } + return; + } + } + } +} + +void BraveVPNRegionDataManager::LoadCachedRegionData() { + // Already loaded from cache. + if (!regions_.empty()) { + return; + } + + // Empty device region means it's initial state. + if (GetDeviceRegion().empty()) { + return; + } + + auto* preference = local_prefs_->FindPreference(prefs::kBraveVPNRegionList); + DCHECK(preference); + // Early return when we don't have any cached region data. + if (preference->IsDefaultValue()) { + return; + } + + // If cached one is outdated, don't use it. + if (!ValidateCachedRegionData(preference->GetValue()->GetList())) { + VLOG(2) << __func__ << " : Cached data is outdate. Will get fetch latest."; + return; + } + + if (ParseAndCacheRegionList(preference->GetValue()->GetList())) { + VLOG(2) << __func__ << " : Loaded cached region list"; + return; + } + + VLOG(2) << __func__ << " : Failed to load cached region list"; +} + +bool BraveVPNRegionDataManager::NeedToUpdateRegionData() const { + if (!IsRegionDataReady()) { + return true; + } + + // Skip checking region data update when we have cached one and its age is + // younger than 5h. + const auto last_fetched_date = + local_prefs_->GetTime(prefs::kBraveVPNRegionListFetchedDate); + constexpr int kRegionDataFetchIntervalInHours = 5; + + if (last_fetched_date.is_null() || + (base::Time::Now() - last_fetched_date).InHours() >= + kRegionDataFetchIntervalInHours) { + return true; + } + + return false; +} + +void BraveVPNRegionDataManager::NotifyRegionDataReady() const { + if (region_data_ready_callback_) { + region_data_ready_callback_.Run(!regions_.empty()); + } +} + +void BraveVPNRegionDataManager::FetchRegionDataIfNeeded() { + if (api_request_) { + VLOG(2) << __func__ << " : Region data fetching is in-progress"; + return; + } + + if (!NeedToUpdateRegionData()) { + VLOG(2) + << __func__ + << " : Don't need to check as it's not passed 5h since the last check."; + NotifyRegionDataReady(); + return; + } + + api_request_ = std::make_unique(url_loader_factory_); + VLOG(2) << __func__ << " : Start fetching region data"; + + // Unretained is safe here becasue this class owns |api_request_|. + api_request_->GetAllServerRegions(base::BindOnce( + &BraveVPNRegionDataManager::OnFetchRegionList, base::Unretained(this))); +} + +void BraveVPNRegionDataManager::OnFetchRegionList( + const std::string& region_list, + bool success) { + if (!api_request_) { + CHECK_IS_TEST(); + } + api_request_.reset(); + + absl::optional value = base::JSONReader::Read(region_list); + if (value && value->is_list() && + ParseAndCacheRegionList(value->GetList(), true)) { + VLOG(2) << "Got valid region list"; + // Set default device region and it'll be updated when received valid + // timezone info. + SetFallbackDeviceRegion(); + // Fetch timezones list to determine default region of this device. + api_request_ = std::make_unique(url_loader_factory_); + api_request_->GetTimezonesForRegions(base::BindOnce( + &BraveVPNRegionDataManager::OnFetchTimezones, base::Unretained(this))); + return; + } + + VLOG(2) << "Got invalid region list"; + NotifyRegionDataReady(); +} + +bool BraveVPNRegionDataManager::ParseAndCacheRegionList( + const base::Value::List& region_value, + bool save_to_prefs) { + const auto new_regions = ParseRegionList(region_value); + VLOG(2) << __func__ << " : has regionlist: " << !new_regions.empty(); + + // To avoid deleting current valid |regions_|, only assign when + // |new_regions| is not empty. + if (new_regions.empty()) { + return false; + } + + regions_ = new_regions; + + if (save_to_prefs) { + SetRegionListToPrefs(); + } + return true; +} + +void BraveVPNRegionDataManager::OnFetchTimezones( + const std::string& timezones_list, + bool success) { + api_request_.reset(); + + absl::optional value = base::JSONReader::Read(timezones_list); + if (success && value && value->is_list()) { + VLOG(2) << "Got valid timezones list"; + SetDeviceRegionWithTimezone(value->GetList()); + } else { + VLOG(2) << "Failed to get invalid timezones list"; + } + + // Can notify as ready now regardless of timezone fetching result. + // We use default one picked from region list as a device region on failure. + NotifyRegionDataReady(); +} + +void BraveVPNRegionDataManager::SetRegionListToPrefs() { + DCHECK(!regions_.empty()); + + base::Value::List regions_list; + for (const auto& region : regions_) { + regions_list.Append(GetValueFromRegion(region)); + } + + local_prefs_->Set(prefs::kBraveVPNRegionList, + base::Value(std::move(regions_list))); + local_prefs_->SetTime(prefs::kBraveVPNRegionListFetchedDate, + base::Time::Now()); +} + +std::string BraveVPNRegionDataManager::GetCurrentTimeZone() { + if (!test_timezone_.empty()) { + return test_timezone_; + } + + return GetTimeZoneName(); +} + +} // namespace brave_vpn diff --git a/components/brave_vpn/browser/connection/brave_vpn_region_data_manager.h b/components/brave_vpn/browser/connection/brave_vpn_region_data_manager.h new file mode 100644 index 000000000000..7510cdb6e4b2 --- /dev/null +++ b/components/brave_vpn/browser/connection/brave_vpn_region_data_manager.h @@ -0,0 +1,88 @@ +/* Copyright (c) 2023 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_BRAVE_VPN_BROWSER_CONNECTION_BRAVE_VPN_REGION_DATA_MANAGER_H_ +#define BRAVE_COMPONENTS_BRAVE_VPN_BROWSER_CONNECTION_BRAVE_VPN_REGION_DATA_MANAGER_H_ + +#include +#include +#include + +#include "base/functional/callback_forward.h" +#include "base/memory/raw_ptr.h" +#include "base/memory/scoped_refptr.h" +#include "brave/components/brave_vpn/browser/api/brave_vpn_api_request.h" +#include "brave/components/brave_vpn/common/mojom/brave_vpn.mojom.h" +#include "services/network/public/cpp/shared_url_loader_factory.h" + +class PrefService; + +namespace brave_vpn { + +class BraveVPNRegionDataManager { + public: + BraveVPNRegionDataManager( + scoped_refptr url_loader_factory, + PrefService* local_prefs); + ~BraveVPNRegionDataManager(); + BraveVPNRegionDataManager(const BraveVPNRegionDataManager&) = delete; + BraveVPNRegionDataManager& operator=(const BraveVPNRegionDataManager&) = + delete; + + const std::vector& GetRegions() const; + bool IsRegionDataReady() const; + std::string GetSelectedRegion() const; + void FetchRegionDataIfNeeded(); + + private: + friend class BraveVPNOSConnectionAPIBase; + friend class BraveVPNServiceTest; + friend class BraveVPNOSConnectionAPIUnitTest; + + void set_selected_region_changed_callback( + base::RepeatingCallback callback) { + selected_region_changed_callback_ = callback; + } + + void set_region_data_ready_callback( + base::RepeatingCallback callback) { + region_data_ready_callback_ = callback; + } + + std::string GetDeviceRegion() const; + void SetDeviceRegion(const std::string& name); + void SetSelectedRegion(const std::string& name); + void SetFallbackDeviceRegion(); + void SetDeviceRegionWithTimezone(const base::Value::List& timezons_value); + + void LoadCachedRegionData(); + void OnFetchRegionList(const std::string& region_list, bool success); + bool ParseAndCacheRegionList(const base::Value::List& region_value, + bool save_to_prefs = false); + void OnFetchTimezones(const std::string& timezones_list, bool success); + void SetRegionListToPrefs(); + + // Notify it's ready when |regions_| is not empty. + std::string GetCurrentTimeZone(); + bool NeedToUpdateRegionData() const; + void NotifyRegionDataReady() const; + + // For testing only. + std::string test_timezone_; + + std::vector regions_; + + // Only not null when region_data fetching is in-progress. + scoped_refptr url_loader_factory_; + raw_ptr local_prefs_ = nullptr; + std::unique_ptr api_request_; + base::RepeatingCallback + selected_region_changed_callback_; + base::RepeatingCallback region_data_ready_callback_; +}; + +} // namespace brave_vpn + +#endif // BRAVE_COMPONENTS_BRAVE_VPN_BROWSER_CONNECTION_BRAVE_VPN_REGION_DATA_MANAGER_H_ diff --git a/components/brave_vpn/common/brave_vpn_utils.cc b/components/brave_vpn/common/brave_vpn_utils.cc index 3da8e34b7012..af101db7c194 100644 --- a/components/brave_vpn/common/brave_vpn_utils.cc +++ b/components/brave_vpn/common/brave_vpn_utils.cc @@ -30,6 +30,7 @@ namespace { void RegisterVPNLocalStatePrefs(PrefRegistrySimple* registry) { #if !BUILDFLAG(IS_ANDROID) registry->RegisterListPref(prefs::kBraveVPNRegionList); + registry->RegisterTimePref(prefs::kBraveVPNRegionListFetchedDate, {}); registry->RegisterStringPref(prefs::kBraveVPNDeviceRegion, ""); registry->RegisterStringPref(prefs::kBraveVPNSelectedRegion, ""); #endif diff --git a/components/brave_vpn/common/pref_names.h b/components/brave_vpn/common/pref_names.h index 0c6a86a51e59..e5cc65223d76 100644 --- a/components/brave_vpn/common/pref_names.h +++ b/components/brave_vpn/common/pref_names.h @@ -15,6 +15,9 @@ constexpr char kBraveVPNLocalStateMigrated[] = "brave.brave_vpn.migrated"; constexpr char kBraveVPNRootPref[] = "brave.brave_vpn"; constexpr char kBraveVPNShowButton[] = "brave.brave_vpn.show_button"; constexpr char kBraveVPNRegionList[] = "brave.brave_vpn.region_list"; +// Cached fetched date for trying to refresh region_list once per day +constexpr char kBraveVPNRegionListFetchedDate[] = + "brave.brave_vpn.region_list_fetched_date"; constexpr char kBraveVPNDeviceRegion[] = "brave.brave_vpn.device_region_name"; constexpr char kBraveVPNSelectedRegion[] = "brave.brave_vpn.selected_region_name";