| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/push_notification/push_notification_service_desktop_impl.h" |
| |
| #include "base/check.h" |
| #include "base/functional/bind.h" |
| #include "chrome/browser/push_notification/metrics/push_notification_metrics.h" |
| #include "chrome/browser/push_notification/prefs/push_notification_prefs.h" |
| #include "chrome/browser/push_notification/protos/notifications_multi_login_update.pb.h" |
| #include "chrome/browser/push_notification/server_client/push_notification_desktop_api_call_flow_impl.h" |
| #include "chrome/browser/push_notification/server_client/push_notification_server_client.h" |
| #include "chrome/browser/push_notification/server_client/push_notification_server_client_desktop_impl.h" |
| #include "chromeos/ash/components/nearby/common/scheduling/nearby_scheduler_factory.h" |
| #include "components/gcm_driver/gcm_driver.h" |
| #include "components/gcm_driver/gcm_profile_service.h" |
| #include "components/gcm_driver/instance_id/instance_id.h" |
| #include "components/gcm_driver/instance_id/instance_id_driver.h" |
| #include "components/gcm_driver/instance_id/instance_id_profile_service.h" |
| #include "components/prefs/pref_service.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| |
| namespace { |
| |
| const char kPushNotificationAppId[] = "com.google.chrome.push_notification"; |
| const char kPushNotificationScope[] = "GCM"; |
| const char kPushNotificationSenderId[] = "745476177629"; |
| const char kClientId[] = "ChromeDesktop"; |
| |
| } // namespace |
| |
| namespace push_notification { |
| |
| PushNotificationServiceDesktopImpl::PushNotificationServiceDesktopImpl( |
| PrefService* pref_service, |
| instance_id::InstanceIDDriver* instance_id_driver, |
| signin::IdentityManager* identity_manager, |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) |
| : pref_service_(pref_service), |
| instance_id_driver_(instance_id_driver), |
| identity_manager_(identity_manager), |
| url_loader_factory_(url_loader_factory) { |
| CHECK(pref_service_); |
| CHECK(instance_id_driver_); |
| CHECK(identity_manager_); |
| CHECK(url_loader_factory_); |
| initialization_on_demand_scheduler_ = |
| ash::nearby::NearbySchedulerFactory::CreateOnDemandScheduler( |
| /*retry_failures=*/true, /*require_connectivity=*/true, |
| prefs::kPushNotificationRegistrationAttemptBackoffSchedulerPrefName, |
| pref_service_, |
| base::BindRepeating(&PushNotificationServiceDesktopImpl::Initialize, |
| base::Unretained(this)), |
| Feature::NEARBY_INFRA, base::DefaultClock::GetInstance()); |
| initialization_on_demand_scheduler_->Start(); |
| } |
| |
| PushNotificationServiceDesktopImpl::~PushNotificationServiceDesktopImpl() = |
| default; |
| |
| void PushNotificationServiceDesktopImpl::ShutdownHandler() { |
| // Shutdown() should come before and it removes us from the list of app |
| // handlers of gcm::GCMDriver so this shouldn't ever been called. |
| NOTREACHED() << "The Push Notification Service should have removed itself " |
| "from the list of app handlers before this could be called."; |
| } |
| |
| void PushNotificationServiceDesktopImpl::OnStoreReset() { |
| // Reset prefs. |
| pref_service_->SetString( |
| prefs::kPushNotificationRepresentativeTargetIdPrefName, std::string()); |
| } |
| |
| void PushNotificationServiceDesktopImpl::OnMessage( |
| const std::string& app_id, |
| const gcm::IncomingMessage& message) { |
| PushNotificationClientManager::PushNotificationMessage |
| push_notification_message; |
| push_notification_message.sender_id = message.sender_id; |
| push_notification_message.message_id = message.message_id; |
| push_notification_message.collapse_key = message.collapse_key; |
| push_notification_message.raw_data = message.raw_data; |
| |
| for (const auto& data : message.data) { |
| push_notification_message.data.insert_or_assign(data.first, data.second); |
| } |
| |
| client_manager_->NotifyPushNotificationClientOfMessage( |
| push_notification_message); |
| } |
| |
| void PushNotificationServiceDesktopImpl::OnMessagesDeleted( |
| const std::string& app_id) {} |
| void PushNotificationServiceDesktopImpl::OnSendError( |
| const std::string& app_id, |
| const gcm::GCMClient::SendErrorDetails& send_error_details) { |
| NOTREACHED() |
| << "The Push Notification Service shouldn't have sent messages upstream"; |
| } |
| void PushNotificationServiceDesktopImpl::OnSendAcknowledged( |
| const std::string& app_id, |
| const std::string& message_id) { |
| NOTREACHED() |
| << "The Push Notification Service shouldn't have sent messages upstream"; |
| } |
| |
| // Intentional no-op. We don't support encryption/decryption of messages. |
| void PushNotificationServiceDesktopImpl::OnMessageDecryptionFailed( |
| const std::string& app_id, |
| const std::string& message_id, |
| const std::string& error_message) {} |
| |
| // PushNotificationService does not support messages from any other app. |
| bool PushNotificationServiceDesktopImpl::CanHandle( |
| const std::string& app_id) const { |
| return false; |
| } |
| |
| void PushNotificationServiceDesktopImpl::Shutdown() { |
| client_manager_.reset(); |
| token_.clear(); |
| instance_id_driver_->GetInstanceID(kPushNotificationAppId) |
| ->gcm_driver() |
| ->RemoveAppHandler(kPushNotificationAppId); |
| } |
| |
| void PushNotificationServiceDesktopImpl::Initialize() { |
| if (is_initialized_) { |
| return; |
| } |
| |
| instance_id_driver_->GetInstanceID(kPushNotificationAppId) |
| ->GetToken( |
| kPushNotificationSenderId, kPushNotificationScope, |
| /*time_to_live=*/base::TimeDelta(), /*flags=*/{}, |
| base::BindOnce(&PushNotificationServiceDesktopImpl::OnTokenReceived, |
| base::Unretained(this), /*token_request_start_time=*/ |
| base::TimeTicks::Now())); |
| } |
| |
| void PushNotificationServiceDesktopImpl::OnTokenReceived( |
| base::TimeTicks token_request_start_time, |
| const std::string& token, |
| instance_id::InstanceID::Result result) { |
| if (result != instance_id::InstanceID::Result::SUCCESS) { |
| LOG(ERROR) << "Failed to retrieve GCM token: " << result; |
| metrics::RecordPushNotificationGcmTokenRetrievalResult(/*success=*/false); |
| initialization_on_demand_scheduler_->HandleResult(/*success=*/false); |
| return; |
| } |
| |
| metrics::RecordPushNotificationGcmTokenRetrievalResult(/*success=*/true); |
| metrics::RecordPushNotificationServiceTimeToRetrieveToken( |
| /*total_retrieval_time=*/base::TimeTicks::Now() - |
| token_request_start_time); |
| VLOG(1) << "Successfully retrieved GCM token. "; |
| token_ = token; |
| |
| // Add `PushNotificationService` as a GCM app handler. |
| instance_id_driver_->GetInstanceID(kPushNotificationAppId) |
| ->gcm_driver() |
| ->AddAppHandler(kPushNotificationAppId, this); |
| |
| std::string representative_target_id = pref_service_->GetString( |
| prefs::kPushNotificationRepresentativeTargetIdPrefName); |
| |
| // Create the `NotificationsMultiLoginUpdateRequest` proto which is used to |
| // make the registration API call. |
| push_notification::proto::NotificationsMultiLoginUpdateRequest request_proto; |
| request_proto.mutable_target()->set_channel_type( |
| push_notification::proto::ChannelType::GCM_DEVICE_PUSH); |
| request_proto.mutable_target() |
| ->mutable_delivery_address() |
| ->mutable_gcm_device_address() |
| ->set_registration_id(token_); |
| request_proto.mutable_target() |
| ->mutable_delivery_address() |
| ->mutable_gcm_device_address() |
| ->set_application_id(kPushNotificationAppId); |
| |
| // `representative_target_id` is left empty the first time we register with |
| // the Push Notification Service. It is then returned to us in the response |
| // proto and stored in prefs. When we have a stored representative target id, |
| // we use it to help the Push Notification Service stablize the target across |
| // registrations if the GCM registration token changes. |
| if (!representative_target_id.empty()) { |
| request_proto.mutable_target()->set_representative_target_id( |
| representative_target_id); |
| } |
| request_proto.add_registrations(); |
| request_proto.set_registration_reason( |
| push_notification::proto::RegistrationReason::COLLABORATOR_API_CALL); |
| request_proto.set_client_id(kClientId); |
| |
| // Construct a HTTP client for the request. The HTTP client lifetime is |
| // tied to a single request. |
| server_client_ = PushNotificationServerClientDesktopImpl::Factory::Create( |
| std::make_unique<PushNotificationDesktopApiCallFlowImpl>(), |
| identity_manager_, url_loader_factory_); |
| |
| server_client_->RegisterWithPushNotificationService( |
| request_proto, |
| base::BindOnce(&PushNotificationServiceDesktopImpl:: |
| OnPushNotificationRegistrationSuccess, |
| weak_ptr_factory_.GetWeakPtr(), |
| /*api_call_start_time=*/base::TimeTicks::Now()), |
| base::BindOnce(&PushNotificationServiceDesktopImpl:: |
| OnPushNotificationRegistrationFailure, |
| weak_ptr_factory_.GetWeakPtr(), |
| /*api_call_start_time=*/base::TimeTicks::Now())); |
| } |
| |
| void PushNotificationServiceDesktopImpl::OnPushNotificationRegistrationSuccess( |
| base::TimeTicks api_call_start_time, |
| const proto::NotificationsMultiLoginUpdateResponse& response) { |
| metrics:: |
| RecordPushNotificationServiceTimeToReceiveRegistrationSuccessResponse( |
| /*registration_response_time=*/base::TimeTicks::Now() - |
| api_call_start_time); |
| metrics::RecordPushNotificationServiceRegistrationResult(/*success=*/true); |
| VLOG(1) << __func__ << ": Push notification service registration successful"; |
| is_initialized_ = true; |
| server_client_.reset(); |
| initialization_on_demand_scheduler_->HandleResult(/*success=*/true); |
| CHECK(response.registration_results_size() == 1); |
| pref_service_->SetString( |
| prefs::kPushNotificationRepresentativeTargetIdPrefName, |
| response.registration_results(0).target().representative_target_id()); |
| } |
| |
| void PushNotificationServiceDesktopImpl::OnPushNotificationRegistrationFailure( |
| base::TimeTicks api_call_start_time, |
| PushNotificationDesktopApiCallFlow::PushNotificationApiCallFlowError |
| error) { |
| metrics:: |
| RecordPushNotificationServiceTimeToReceiveRegistrationFailureResponse( |
| /*registration_response_time=*/base::TimeTicks::Now() - |
| api_call_start_time); |
| metrics::RecordPushNotificationServiceRegistrationResult(/*success=*/false); |
| LOG(ERROR) << __func__ |
| << ": Push notification service registration failure: " << error; |
| server_client_.reset(); |
| |
| // Remove ourselves as a GCM app handler since initialization failed. |
| instance_id_driver_->GetInstanceID(kPushNotificationAppId) |
| ->gcm_driver() |
| ->RemoveAppHandler(kPushNotificationAppId); |
| |
| initialization_on_demand_scheduler_->HandleResult(/*success=*/false); |
| } |
| |
| } // namespace push_notification |