1 // Copyright 2014 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
5 #include "chrome/browser/services/gcm/push_messaging_service_impl.h"
6
7 #include <vector>
8
9 #include "base/bind.h"
10 #include "base/command_line.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/strings/string_util.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/services/gcm/gcm_profile_service.h"
15 #include "chrome/browser/services/gcm/gcm_profile_service_factory.h"
16 #include "chrome/common/chrome_switches.h"
17 #include "chrome/common/pref_names.h"
18 #include "components/gcm_driver/gcm_driver.h"
19 #include "components/pref_registry/pref_registry_syncable.h"
20
21 namespace gcm {
22
23 namespace {
24 const char kAppIdPrefix[] = "push:";
25 const int kMaxRegistrations = 1000000;
26 } // namespace
27
28 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)29 void PushMessagingServiceImpl::RegisterProfilePrefs(
30 user_prefs::PrefRegistrySyncable* registry) {
31 registry->RegisterIntegerPref(
32 prefs::kPushMessagingRegistrationCount,
33 0,
34 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
35 }
36
37 // static
InitializeForProfile(Profile * profile)38 void PushMessagingServiceImpl::InitializeForProfile(Profile* profile) {
39 // TODO(mvanouwerkerk): Make sure to remove this check at the same time as
40 // push graduates from experimental in Blink.
41 if (!CommandLine::ForCurrentProcess()->HasSwitch(
42 switches::kEnableExperimentalWebPlatformFeatures)) {
43 return;
44 }
45 // TODO(johnme): Consider whether push should be enabled in incognito. If it
46 // does get enabled, then be careful that you're reading the pref from the
47 // right profile, as prefs defined in a regular profile are visible in the
48 // corresponding incognito profile unless overrriden.
49 if (!profile || profile->IsOffTheRecord() ||
50 profile->GetPrefs()->GetInteger(prefs::kPushMessagingRegistrationCount) <=
51 0) {
52 return;
53 }
54 // Create the GCMProfileService, and hence instantiate this class.
55 GCMProfileService* gcm_service =
56 gcm::GCMProfileServiceFactory::GetForProfile(profile);
57 PushMessagingServiceImpl* push_service =
58 static_cast<PushMessagingServiceImpl*>(
59 gcm_service->push_messaging_service());
60 // Register ourselves as an app handler.
61 gcm_service->driver()->AddAppHandler(kAppIdPrefix, push_service);
62 }
63
PushMessagingServiceImpl(GCMProfileService * gcm_profile_service,Profile * profile)64 PushMessagingServiceImpl::PushMessagingServiceImpl(
65 GCMProfileService* gcm_profile_service,
66 Profile* profile)
67 : gcm_profile_service_(gcm_profile_service),
68 profile_(profile),
69 weak_factory_(this) {
70 }
71
~PushMessagingServiceImpl()72 PushMessagingServiceImpl::~PushMessagingServiceImpl() {
73 // TODO(johnme): If it's possible for this to be destroyed before GCMDriver,
74 // then we should call RemoveAppHandler.
75 }
76
CanHandle(const std::string & app_id) const77 bool PushMessagingServiceImpl::CanHandle(const std::string& app_id) const {
78 // TODO(mvanouwerkerk): Finalize and centralize format of Push API app_id.
79 return StartsWithASCII(app_id, kAppIdPrefix, true);
80 }
81
ShutdownHandler()82 void PushMessagingServiceImpl::ShutdownHandler() {
83 // TODO(johnme): Do any necessary cleanup.
84 }
85
OnMessage(const std::string & app_id,const GCMClient::IncomingMessage & message)86 void PushMessagingServiceImpl::OnMessage(
87 const std::string& app_id,
88 const GCMClient::IncomingMessage& message) {
89 // The Push API only exposes a single string of data in the push event fired
90 // on the Service Worker. When developers send messages using GCM to the Push
91 // API, they must pass a single key-value pair, where the key is "data" and
92 // the value is the string they want to be passed to their Service Worker.
93 // For example, they could send the following JSON using the HTTPS GCM API:
94 // {
95 // "registration_ids": ["FOO", "BAR"],
96 // "data": {
97 // "data": "BAZ",
98 // },
99 // "delay_while_idle": true,
100 // }
101 // TODO(johnme): Make sure this is clearly documented for developers.
102 GCMClient::MessageData::const_iterator it = message.data.find("data");
103 if (it != message.data.end()) {
104 const std::string& data ALLOW_UNUSED = it->second;
105 // TODO(mvanouwerkerk): Fire push event with data on the Service Worker
106 // corresponding to app_id (and remove ALLOW_UNUSED above).
107 } else {
108 // Drop the message, as it is invalid.
109 // TODO(mvanouwerkerk): Show a warning in the developer console of the
110 // Service Worker corresponding to app_id.
111 // TODO(johnme): Add diagnostic observers (e.g. UMA and an internals page)
112 // to know when bad things happen.
113 }
114 }
115
OnMessagesDeleted(const std::string & app_id)116 void PushMessagingServiceImpl::OnMessagesDeleted(const std::string& app_id) {
117 // TODO(mvanouwerkerk): Fire push error event on the Service Worker
118 // corresponding to app_id.
119 }
120
OnSendError(const std::string & app_id,const GCMClient::SendErrorDetails & send_error_details)121 void PushMessagingServiceImpl::OnSendError(
122 const std::string& app_id,
123 const GCMClient::SendErrorDetails& send_error_details) {
124 NOTREACHED() << "The Push API shouldn't have sent messages upstream";
125 }
126
Register(const std::string & app_id,const std::string & sender_id,const content::PushMessagingService::RegisterCallback & callback)127 void PushMessagingServiceImpl::Register(
128 const std::string& app_id,
129 const std::string& sender_id,
130 const content::PushMessagingService::RegisterCallback& callback) {
131 if (!gcm_profile_service_->driver()) {
132 NOTREACHED() << "There is no GCMDriver. Has GCMProfileService shut down?";
133 }
134
135 if (profile_->GetPrefs()->GetInteger(
136 prefs::kPushMessagingRegistrationCount) >= kMaxRegistrations) {
137 DidRegister(app_id, callback, "", GCMClient::UNKNOWN_ERROR);
138 return;
139 }
140
141 // If this is registering for the first time then the driver does not have
142 // this as an app handler and registration would fail.
143 if (gcm_profile_service_->driver()->GetAppHandler(kAppIdPrefix) != this)
144 gcm_profile_service_->driver()->AddAppHandler(kAppIdPrefix, this);
145
146 std::vector<std::string> sender_ids(1, sender_id);
147 gcm_profile_service_->driver()->Register(
148 app_id,
149 sender_ids,
150 base::Bind(&PushMessagingServiceImpl::DidRegister,
151 weak_factory_.GetWeakPtr(),
152 app_id,
153 callback));
154 }
155
DidRegister(const std::string & app_id,const content::PushMessagingService::RegisterCallback & callback,const std::string & registration_id,GCMClient::Result result)156 void PushMessagingServiceImpl::DidRegister(
157 const std::string& app_id,
158 const content::PushMessagingService::RegisterCallback& callback,
159 const std::string& registration_id,
160 GCMClient::Result result) {
161 GURL endpoint = GURL("https://android.googleapis.com/gcm/send");
162 bool success = (result == GCMClient::SUCCESS);
163 callback.Run(endpoint, registration_id, success);
164 if (success) {
165 // TODO(johnme): Make sure the pref doesn't get out of sync after crashes.
166 int registration_count = profile_->GetPrefs()->GetInteger(
167 prefs::kPushMessagingRegistrationCount);
168 profile_->GetPrefs()->SetInteger(prefs::kPushMessagingRegistrationCount,
169 registration_count + 1);
170 }
171 }
172
173 // TODO(johnme): Unregister should decrement the pref, and call
174 // RemoveAppHandler if the count drops to zero.
175
176 } // namespace gcm
177