blob: 883aca391c29aa06be4527ee1193a2643fce2030 [file] [log] [blame]
// Copyright 2018 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.
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#import "remoting/ios/facade/host_list_service.h"
#import <CoreFoundation/CoreFoundation.h>
#import "remoting/ios/domain/host_info.h"
#import "remoting/ios/domain/user_info.h"
#import "remoting/ios/facade/host_info.h"
#import "remoting/ios/facade/host_list_fetcher.h"
#import "remoting/ios/facade/remoting_authentication.h"
#import "remoting/ios/facade/remoting_service.h"
#include "base/i18n/time_formatting.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "net/url_request/url_fetcher.h"
#include "remoting/base/string_resources.h"
#include "ui/base/l10n/l10n_util.h"
namespace remoting {
namespace {
bool IsValidErrorCode(int error_code) {
#define HTTP_STATUS(label, code, reason) \
if (error_code == code) \
return true;
#include "net/http/http_status_code_list.h"
#undef HTTP_STATUS
return false;
}
std::string GetRequestErrorMessage(int error_code) {
if (IsValidErrorCode(error_code)) {
std::string error_phrase =
net::GetHttpReasonPhrase(static_cast<net::HttpStatusCode>(error_code));
return l10n_util::GetStringFUTF8(IDS_SERVER_COMMUNICATION_ERROR,
base::UTF8ToUTF16(error_phrase));
}
switch (error_code) {
case net::URLFetcher::RESPONSE_CODE_INVALID:
return l10n_util::GetStringUTF8(IDS_ERROR_NETWORK_ERROR);
default:
return l10n_util::GetStringFUTF8(IDS_SERVER_COMMUNICATION_ERROR,
base::IntToString16(error_code));
}
}
} // namespace
HostListService* HostListService::GetInstance() {
static base::NoDestructor<HostListService> instance;
return instance.get();
}
HostListService::HostListService() : weak_factory_(this) {
auto weak_this = weak_factory_.GetWeakPtr();
user_update_observer_ = [NSNotificationCenter.defaultCenter
addObserverForName:kUserDidUpdate
object:nil
queue:nil
usingBlock:^(NSNotification* notification) {
UserInfo* user = notification.userInfo[kUserInfo];
if (weak_this) {
weak_this->OnUserUpdated(user != nil);
}
}];
}
HostListService::~HostListService() {
[NSNotificationCenter.defaultCenter removeObserver:user_update_observer_];
}
std::unique_ptr<HostListService::CallbackSubscription>
HostListService::RegisterHostListStateCallback(
const base::RepeatingClosure& callback) {
return host_list_state_callbacks_.Add(callback);
}
std::unique_ptr<HostListService::CallbackSubscription>
HostListService::RegisterFetchFailureCallback(
const base::RepeatingClosure& callback) {
return fetch_failure_callbacks_.Add(callback);
}
void HostListService::RequestFetch() {
auto weak_this = weak_factory_.GetWeakPtr();
[RemotingService.instance.authentication
callbackWithAccessToken:^(RemotingAuthenticationStatus status,
NSString* userEmail, NSString* accessToken) {
if (status == RemotingAuthenticationStatusSuccess) {
if (weak_this) {
weak_this->StartHostListFetch(base::SysNSStringToUTF8(accessToken));
}
return;
}
FetchFailureReason failureReason;
switch (status) {
case RemotingAuthenticationStatusNetworkError:
failureReason = FetchFailureReason::NETWORK_ERROR;
break;
case RemotingAuthenticationStatusAuthError:
failureReason = FetchFailureReason::AUTH_ERROR;
break;
default:
NOTREACHED();
failureReason = FetchFailureReason::NETWORK_ERROR;
}
if (weak_this) {
weak_this->HandleFetchFailure(failureReason, 0);
}
}];
}
void HostListService::SetHostListFetcherForTesting(
std::unique_ptr<HostListFetcher> fetcher) {
host_list_fetcher_ = std::move(fetcher);
}
// static
std::unique_ptr<HostListService> HostListService::CreateInstanceForTesting() {
return std::make_unique<HostListService>();
}
void HostListService::SetState(State state) {
if (state == state_) {
return;
}
if (state == State::NOT_FETCHED) {
hosts_ = {};
} else if (state == State::FETCHING || state == State::FETCHED) {
last_fetch_failure_.reset();
}
state_ = state;
host_list_state_callbacks_.Notify();
}
void HostListService::StartHostListFetch(const std::string& access_token) {
if (state_ == State::FETCHING) {
return;
}
SetState(State::FETCHING);
if (!host_list_fetcher_) {
host_list_fetcher_.reset(new HostListFetcher(
ChromotingClientRuntime::GetInstance()->url_requester()));
}
host_list_fetcher_->RetrieveHostlist(
access_token, base::BindOnce(&HostListService::HandleHostListResult,
weak_factory_.GetWeakPtr()));
}
void HostListService::HandleHostListResult(
int responseCode,
const std::vector<remoting::HostInfo>& hostlist) {
if (responseCode == net::HTTP_OK) {
hosts_ = hostlist;
SetState(State::FETCHED);
return;
}
if (responseCode != HostListFetcher::RESPONSE_CODE_CANCELLED) {
if (responseCode == net::HTTP_UNAUTHORIZED) {
[RemotingService.instance.authentication logout];
} else {
HandleFetchFailure(FetchFailureReason::REQUEST_ERROR, responseCode);
}
}
SetState(State::NOT_FETCHED);
}
void HostListService::HandleFetchFailure(FetchFailureReason reason,
int error_code) {
last_fetch_failure_ = std::make_unique<FetchFailureInfo>();
last_fetch_failure_->reason = reason;
last_fetch_failure_->error_code = error_code;
switch (reason) {
case FetchFailureReason::NETWORK_ERROR:
last_fetch_failure_->localized_description =
l10n_util::GetStringUTF8(IDS_ERROR_NETWORK_ERROR);
break;
case FetchFailureReason::AUTH_ERROR:
last_fetch_failure_->localized_description =
l10n_util::GetStringUTF8(IDS_ERROR_OAUTH_TOKEN_INVALID);
break;
case FetchFailureReason::REQUEST_ERROR:
last_fetch_failure_->localized_description =
GetRequestErrorMessage(error_code);
break;
default:
NOTREACHED();
}
LOG(WARNING) << "Failed to fetch host list: "
<< last_fetch_failure_->localized_description
<< " reason: " << static_cast<int>(reason)
<< ", error_code: " << error_code;
fetch_failure_callbacks_.Notify();
}
void HostListService::OnUserUpdated(bool is_user_signed_in) {
if (host_list_fetcher_) {
host_list_fetcher_->CancelFetch();
}
SetState(State::NOT_FETCHED);
if (is_user_signed_in) {
RequestFetch();
}
}
} // namespace remoting