// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/extensions/api/copresence/copresence_api.h" #include #include "base/lazy_instance.h" #include "base/memory/linked_ptr.h" #include "base/prefs/pref_service.h" #include "chrome/browser/copresence/chrome_whispernet_client.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/services/gcm/gcm_profile_service_factory.h" #include "chrome/common/channel_info.h" #include "chrome/common/extensions/api/copresence.h" #include "chrome/common/extensions/manifest_handlers/copresence_manifest.h" #include "chrome/common/pref_names.h" #include "components/copresence/copresence_manager_impl.h" #include "components/copresence/proto/data.pb.h" #include "components/copresence/proto/enums.pb.h" #include "components/copresence/proto/rpcs.pb.h" #include "components/gcm_driver/gcm_profile_service.h" #include "components/pref_registry/pref_registry_syncable.h" #include "content/public/browser/browser_context.h" #include "extensions/browser/event_router.h" #include "extensions/browser/extension_registry.h" #include "extensions/common/extension.h" #include "extensions/common/manifest_constants.h" using user_prefs::PrefRegistrySyncable; namespace extensions { namespace { base::LazyInstance> g_factory = LAZY_INSTANCE_INITIALIZER; const char kInvalidOperationsMessage[] = "Invalid operation in operations array."; const char kShuttingDownMessage[] = "Shutting down."; const std::string GetPrefName(bool authenticated) { return authenticated ? prefs::kCopresenceAuthenticatedDeviceId : prefs::kCopresenceAnonymousDeviceId; } } // namespace namespace Execute = api::copresence::Execute; namespace OnMessagesReceived = api::copresence::OnMessagesReceived; namespace OnStatusUpdated = api::copresence::OnStatusUpdated; namespace SetApiKey = api::copresence::SetApiKey; namespace SetAuthToken = api::copresence::SetAuthToken; // Public functions. CopresenceService::CopresenceService(content::BrowserContext* context) : is_shutting_down_(false), browser_context_(context) {} CopresenceService::~CopresenceService() {} void CopresenceService::Shutdown() { is_shutting_down_ = true; manager_.reset(); whispernet_client_.reset(); } copresence::CopresenceManager* CopresenceService::manager() { if (!manager_ && !is_shutting_down_) manager_.reset(new copresence::CopresenceManagerImpl(this)); return manager_.get(); } std::string CopresenceService::auth_token(const std::string& app_id) const { // This won't be const if we use map[] const auto& key = auth_tokens_by_app_.find(app_id); return key == auth_tokens_by_app_.end() ? std::string() : key->second; } void CopresenceService::set_api_key(const std::string& app_id, const std::string& api_key) { DCHECK(!app_id.empty()); api_keys_by_app_[app_id] = api_key; } void CopresenceService::set_auth_token(const std::string& app_id, const std::string& token) { DCHECK(!app_id.empty()); auth_tokens_by_app_[app_id] = token; } void CopresenceService::set_manager_for_testing( scoped_ptr manager) { manager_ = std::move(manager); } void CopresenceService::ResetState() { DVLOG(2) << "Deleting copresence state"; GetPrefService()->ClearPref(prefs::kCopresenceAuthenticatedDeviceId); GetPrefService()->ClearPref(prefs::kCopresenceAnonymousDeviceId); manager_ = nullptr; } // static void CopresenceService::RegisterProfilePrefs(PrefRegistrySyncable* registry) { registry->RegisterStringPref(prefs::kCopresenceAuthenticatedDeviceId, std::string()); registry->RegisterStringPref(prefs::kCopresenceAnonymousDeviceId, std::string()); } // static BrowserContextKeyedAPIFactory* CopresenceService::GetFactoryInstance() { return g_factory.Pointer(); } // Private functions. void CopresenceService::HandleMessages( const std::string& /* app_id */, const std::string& subscription_id, const std::vector& messages) { // TODO(ckehoe): Once the server starts sending back the app ids associated // with subscriptions, use that instead of the apps_by_subs registry. std::string app_id = apps_by_subscription_id_[subscription_id]; if (app_id.empty()) { LOG(ERROR) << "Skipping message from unrecognized subscription " << subscription_id; return; } int message_count = messages.size(); std::vector> api_messages( message_count); for (int m = 0; m < message_count; ++m) { api_messages[m].reset(new api::copresence::Message); api_messages[m]->type = messages[m].type().type(); api_messages[m]->payload.assign(messages[m].payload().begin(), messages[m].payload().end()); DVLOG(2) << "Dispatching message of type " << api_messages[m]->type << ":\n" << messages[m].payload(); } // Send the messages to the client app. scoped_ptr event(new Event( events::COPRESENCE_ON_MESSAGES_RECEIVED, OnMessagesReceived::kEventName, OnMessagesReceived::Create(subscription_id, api_messages), browser_context_)); EventRouter::Get(browser_context_) ->DispatchEventToExtension(app_id, std::move(event)); DVLOG(2) << "Passed " << api_messages.size() << " messages to app \"" << app_id << "\" for subscription \"" << subscription_id << "\""; } void CopresenceService::HandleStatusUpdate( copresence::CopresenceStatus status) { DCHECK_EQ(copresence::AUDIO_FAIL, status); scoped_ptr event(new Event( events::COPRESENCE_ON_STATUS_UPDATED, OnStatusUpdated::kEventName, OnStatusUpdated::Create(api::copresence::STATUS_AUDIOFAILED), browser_context_)); EventRouter::Get(browser_context_)->BroadcastEvent(std::move(event)); DVLOG(2) << "Sent Audio Failed status update."; } net::URLRequestContextGetter* CopresenceService::GetRequestContext() const { return browser_context_->GetRequestContext(); } std::string CopresenceService::GetPlatformVersionString() const { return chrome::GetVersionString(); } std::string CopresenceService::GetAPIKey(const std::string& app_id) const { // Check first if the app has set its key via the API. const auto& key = api_keys_by_app_.find(app_id); if (key != api_keys_by_app_.end()) return key->second; // If no key was found, look in the manifest. if (!app_id.empty()) { const Extension* extension = ExtensionRegistry::Get(browser_context_) ->GetExtensionById(app_id, ExtensionRegistry::ENABLED); DCHECK(extension) << "Invalid extension ID"; CopresenceManifestData* manifest_data = static_cast( extension->GetManifestData(manifest_keys::kCopresence)); if (manifest_data) return manifest_data->api_key; } return std::string(); } audio_modem::WhispernetClient* CopresenceService::GetWhispernetClient() { if (!whispernet_client_ && !is_shutting_down_) whispernet_client_.reset(new ChromeWhispernetClient(browser_context_)); return whispernet_client_.get(); } gcm::GCMDriver* CopresenceService::GetGCMDriver() { gcm::GCMProfileService* gcm_service = gcm::GCMProfileServiceFactory::GetForProfile(browser_context_); return gcm_service ? gcm_service->driver() : nullptr; } std::string CopresenceService::GetDeviceId(bool authenticated) { std::string id = GetPrefService()->GetString(GetPrefName(authenticated)); DVLOG(3) << "Retrieved device ID \"" << id << "\", " << "authenticated = " << authenticated; return id; } void CopresenceService::SaveDeviceId(bool authenticated, const std::string& device_id) { DVLOG(3) << "Storing device ID \"" << device_id << "\", " << "authenticated = " << authenticated; if (device_id.empty()) GetPrefService()->ClearPref(GetPrefName(authenticated)); else GetPrefService()->SetString(GetPrefName(authenticated), device_id); } PrefService* CopresenceService::GetPrefService() { return Profile::FromBrowserContext(browser_context_)->GetPrefs(); } template <> void BrowserContextKeyedAPIFactory::DeclareFactoryDependencies() { DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); } // CopresenceExecuteFunction implementation. ExtensionFunction::ResponseAction CopresenceExecuteFunction::Run() { scoped_ptr params(Execute::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); CopresenceService* service = CopresenceService::GetFactoryInstance()->Get(browser_context()); // This can only happen if we're shutting down. In all other cases, if we // don't have a manager, we'll create one. if (!service->manager()) return RespondNow(Error(kShuttingDownMessage)); // Each execute will correspond to one ReportRequest protocol buffer. copresence::ReportRequest request; if (!PrepareReportRequestProto(params->operations, extension_id(), &service->apps_by_subscription_id(), &request)) { return RespondNow(Error(kInvalidOperationsMessage)); } service->manager()->ExecuteReportRequest( request, extension_id(), service->auth_token(extension_id()), base::Bind(&CopresenceExecuteFunction::SendResult, this)); return RespondLater(); } void CopresenceExecuteFunction::SendResult( copresence::CopresenceStatus status) { api::copresence::ExecuteStatus api_status = (status == copresence::SUCCESS) ? api::copresence::EXECUTE_STATUS_SUCCESS : api::copresence::EXECUTE_STATUS_FAILED; Respond(ArgumentList(Execute::Results::Create(api_status))); } // CopresenceSetApiKeyFunction implementation. ExtensionFunction::ResponseAction CopresenceSetApiKeyFunction::Run() { scoped_ptr params(SetApiKey::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); LOG(WARNING) << "copresence.setApiKey() is deprecated. " << "Put the key in the manifest at copresence.api_key instead."; // The api key may be set to empty, to clear it. CopresenceService::GetFactoryInstance()->Get(browser_context()) ->set_api_key(extension_id(), params->api_key); return RespondNow(NoArguments()); } // CopresenceSetAuthTokenFunction implementation ExtensionFunction::ResponseAction CopresenceSetAuthTokenFunction::Run() { scoped_ptr params(SetAuthToken::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); // The token may be set to empty, to clear it. CopresenceService::GetFactoryInstance()->Get(browser_context()) ->set_auth_token(extension_id(), params->token); return RespondNow(NoArguments()); } } // namespace extensions