fcm_invalidation_listener.cc 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. // Copyright 2018 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/invalidation/impl/fcm_invalidation_listener.h"
  5. #include "base/bind.h"
  6. #include "base/callback.h"
  7. #include "base/logging.h"
  8. #include "base/threading/thread_task_runner_handle.h"
  9. #include "components/invalidation/public/invalidation_util.h"
  10. #include "components/invalidation/public/topic_invalidation_map.h"
  11. #include "components/prefs/pref_service.h"
  12. namespace invalidation {
  13. FCMInvalidationListener::FCMInvalidationListener(
  14. std::unique_ptr<FCMSyncNetworkChannel> network_channel)
  15. : network_channel_(std::move(network_channel)) {
  16. network_channel_->AddObserver(this);
  17. }
  18. FCMInvalidationListener::~FCMInvalidationListener() {
  19. network_channel_->RemoveObserver(this);
  20. Stop();
  21. DCHECK(!delegate_);
  22. }
  23. void FCMInvalidationListener::Start(
  24. Delegate* delegate,
  25. std::unique_ptr<PerUserTopicSubscriptionManager>
  26. per_user_topic_subscription_manager) {
  27. DCHECK(delegate);
  28. Stop();
  29. delegate_ = delegate;
  30. per_user_topic_subscription_manager_ =
  31. std::move(per_user_topic_subscription_manager);
  32. per_user_topic_subscription_manager_->Init();
  33. per_user_topic_subscription_manager_->AddObserver(this);
  34. network_channel_->SetMessageReceiver(
  35. base::BindRepeating(&FCMInvalidationListener::InvalidationReceived,
  36. weak_factory_.GetWeakPtr()));
  37. network_channel_->SetTokenReceiver(base::BindRepeating(
  38. &FCMInvalidationListener::TokenReceived, weak_factory_.GetWeakPtr()));
  39. subscription_channel_state_ = SubscriptionChannelState::ENABLED;
  40. network_channel_->StartListening();
  41. EmitStateChange();
  42. DoSubscriptionUpdate();
  43. }
  44. void FCMInvalidationListener::UpdateInterestedTopics(const Topics& topics) {
  45. topics_update_requested_ = true;
  46. interested_topics_ = topics;
  47. DoSubscriptionUpdate();
  48. }
  49. void FCMInvalidationListener::ClearInstanceIDToken() {
  50. TokenReceived(std::string());
  51. }
  52. void FCMInvalidationListener::InvalidationReceived(
  53. const std::string& payload,
  54. const std::string& private_topic,
  55. const std::string& public_topic,
  56. int64_t version) {
  57. // Note: |public_topic| is empty for some invalidations (e.g. Drive). Prefer
  58. // using |*expected_public_topic| over |public_topic|.
  59. absl::optional<std::string> expected_public_topic =
  60. per_user_topic_subscription_manager_
  61. ->LookupSubscribedPublicTopicByPrivateTopic(private_topic);
  62. if (!expected_public_topic ||
  63. (!public_topic.empty() && public_topic != *expected_public_topic)) {
  64. DVLOG(1) << "Unexpected invalidation for " << private_topic
  65. << " with public topic " << public_topic << ". Expected "
  66. << expected_public_topic.value_or("<None>");
  67. return;
  68. }
  69. TopicInvalidationMap invalidations;
  70. Invalidation inv =
  71. Invalidation::Init(*expected_public_topic, version, payload);
  72. inv.SetAckHandler(weak_factory_.GetWeakPtr(),
  73. base::ThreadTaskRunnerHandle::Get());
  74. DVLOG(1) << "Received invalidation with version " << inv.version() << " for "
  75. << *expected_public_topic;
  76. invalidations.Insert(inv);
  77. DispatchInvalidations(invalidations);
  78. }
  79. void FCMInvalidationListener::DispatchInvalidations(
  80. const TopicInvalidationMap& invalidations) {
  81. TopicInvalidationMap to_save = invalidations;
  82. TopicInvalidationMap to_emit =
  83. invalidations.GetSubsetWithTopics(interested_topics_);
  84. SaveInvalidations(to_save);
  85. EmitSavedInvalidations(to_emit);
  86. }
  87. void FCMInvalidationListener::SaveInvalidations(
  88. const TopicInvalidationMap& to_save) {
  89. for (const Topic& topic : to_save.GetTopics()) {
  90. auto lookup = unacked_invalidations_map_.find(topic);
  91. if (lookup == unacked_invalidations_map_.end()) {
  92. lookup = unacked_invalidations_map_
  93. .emplace(topic, UnackedInvalidationSet(topic))
  94. .first;
  95. }
  96. lookup->second.AddSet(to_save.ForTopic(topic));
  97. }
  98. }
  99. void FCMInvalidationListener::EmitSavedInvalidations(
  100. const TopicInvalidationMap& to_emit) {
  101. delegate_->OnInvalidate(to_emit);
  102. }
  103. void FCMInvalidationListener::TokenReceived(
  104. const std::string& instance_id_token) {
  105. instance_id_token_ = instance_id_token;
  106. if (instance_id_token_.empty()) {
  107. if (per_user_topic_subscription_manager_) {
  108. per_user_topic_subscription_manager_->ClearInstanceIDToken();
  109. }
  110. } else {
  111. DoSubscriptionUpdate();
  112. }
  113. }
  114. void FCMInvalidationListener::Acknowledge(const Topic& topic,
  115. const AckHandle& handle) {
  116. auto lookup = unacked_invalidations_map_.find(topic);
  117. if (lookup == unacked_invalidations_map_.end()) {
  118. DLOG(WARNING) << "Received acknowledgement for untracked topic";
  119. return;
  120. }
  121. lookup->second.Acknowledge(handle);
  122. }
  123. void FCMInvalidationListener::Drop(const Topic& topic,
  124. const AckHandle& handle) {
  125. auto lookup = unacked_invalidations_map_.find(topic);
  126. if (lookup == unacked_invalidations_map_.end()) {
  127. DLOG(WARNING) << "Received drop for untracked topic";
  128. return;
  129. }
  130. lookup->second.Drop(handle);
  131. }
  132. void FCMInvalidationListener::DoSubscriptionUpdate() {
  133. if (!per_user_topic_subscription_manager_ || instance_id_token_.empty() ||
  134. !topics_update_requested_) {
  135. return;
  136. }
  137. per_user_topic_subscription_manager_->UpdateSubscribedTopics(
  138. interested_topics_, instance_id_token_);
  139. // Go over all stored unacked invalidations and dispatch them if their topics
  140. // have become interesting.
  141. // Note: We might dispatch invalidations for a second time here, if they were
  142. // already dispatched but not acked yet.
  143. // TODO(melandory): remove unacked invalidations for unregistered topics.
  144. TopicInvalidationMap topic_invalidation_map;
  145. for (const auto& unacked : unacked_invalidations_map_) {
  146. if (interested_topics_.find(unacked.first) == interested_topics_.end()) {
  147. continue;
  148. }
  149. unacked.second.ExportInvalidations(weak_factory_.GetWeakPtr(),
  150. base::ThreadTaskRunnerHandle::Get(),
  151. &topic_invalidation_map);
  152. }
  153. // There's no need to run these through DispatchInvalidations(); they've
  154. // already been saved to storage (that's where we found them) so all we need
  155. // to do now is emit them.
  156. EmitSavedInvalidations(topic_invalidation_map);
  157. }
  158. void FCMInvalidationListener::RequestDetailedStatus(
  159. const base::RepeatingCallback<void(base::Value::Dict)>& callback) const {
  160. network_channel_->RequestDetailedStatus(callback);
  161. callback.Run(CollectDebugData());
  162. }
  163. void FCMInvalidationListener::StartForTest(Delegate* delegate) {
  164. delegate_ = delegate;
  165. }
  166. void FCMInvalidationListener::EmitStateChangeForTest(InvalidatorState state) {
  167. delegate_->OnInvalidatorStateChange(state);
  168. }
  169. void FCMInvalidationListener::EmitSavedInvalidationsForTest(
  170. const TopicInvalidationMap& to_emit) {
  171. EmitSavedInvalidations(to_emit);
  172. }
  173. void FCMInvalidationListener::Stop() {
  174. delegate_ = nullptr;
  175. if (per_user_topic_subscription_manager_) {
  176. per_user_topic_subscription_manager_->RemoveObserver(this);
  177. }
  178. per_user_topic_subscription_manager_.reset();
  179. network_channel_->StopListening();
  180. subscription_channel_state_ = SubscriptionChannelState::NOT_STARTED;
  181. fcm_network_state_ = FcmChannelState::NOT_STARTED;
  182. }
  183. InvalidatorState FCMInvalidationListener::GetState() const {
  184. if (subscription_channel_state_ ==
  185. SubscriptionChannelState::ACCESS_TOKEN_FAILURE) {
  186. return INVALIDATION_CREDENTIALS_REJECTED;
  187. }
  188. if (subscription_channel_state_ == SubscriptionChannelState::ENABLED &&
  189. fcm_network_state_ == FcmChannelState::ENABLED) {
  190. // If the ticl is ready and the push client notifications are
  191. // enabled, return INVALIDATIONS_ENABLED.
  192. return INVALIDATIONS_ENABLED;
  193. }
  194. // Otherwise, we have a transient error.
  195. return TRANSIENT_INVALIDATION_ERROR;
  196. }
  197. void FCMInvalidationListener::EmitStateChange() {
  198. delegate_->OnInvalidatorStateChange(GetState());
  199. }
  200. void FCMInvalidationListener::OnFCMChannelStateChanged(FcmChannelState state) {
  201. fcm_network_state_ = state;
  202. EmitStateChange();
  203. }
  204. void FCMInvalidationListener::OnSubscriptionChannelStateChanged(
  205. SubscriptionChannelState state) {
  206. subscription_channel_state_ = state;
  207. EmitStateChange();
  208. }
  209. base::Value::Dict FCMInvalidationListener::CollectDebugData() const {
  210. base::Value::Dict status =
  211. per_user_topic_subscription_manager_->CollectDebugData();
  212. status.SetByDottedPath("InvalidationListener.FCM-channel-state",
  213. FcmChannelStateToString(fcm_network_state_));
  214. status.SetByDottedPath(
  215. "InvalidationListener.Subscription-channel-state",
  216. SubscriptionChannelStateToString(subscription_channel_state_));
  217. for (const auto& topic : interested_topics_) {
  218. if (!status.Find(topic.first)) {
  219. status.Set(topic.first, "Unsubscribed");
  220. }
  221. }
  222. return status;
  223. }
  224. } // namespace invalidation