Skip to content

Commit

Permalink
[Rewards 3.0] Add adaptive captcha modal
Browse files Browse the repository at this point in the history
  • Loading branch information
zenparsing committed Oct 4, 2024
1 parent e9a5f85 commit 59606b8
Show file tree
Hide file tree
Showing 22 changed files with 459 additions and 54 deletions.
2 changes: 1 addition & 1 deletion browser/ui/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,7 @@ source_set("ui") {
"//brave/common",
"//brave/components/ai_chat/core/common/buildflags",
"//brave/components/ai_rewriter/common/buildflags",
"//brave/components/brave_adaptive_captcha",
"//brave/components/brave_adblock_ui:generated_resources",
"//brave/components/brave_adblock_ui/adblock_internals:generated_resources",
"//brave/components/brave_ads/browser",
Expand Down Expand Up @@ -1030,7 +1031,6 @@ source_set("ui") {
"//brave/browser/brave_wallet",
"//brave/browser/resources/settings:resources",
"//brave/common/importer",
"//brave/components/brave_adaptive_captcha",
"//brave/components/brave_new_tab_ui:generated_resources",
"//brave/components/brave_new_tab_ui:mojom",
"//brave/components/brave_news/browser",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ void BraveTooltipPopupHandler::Show(Profile* profile,
DCHECK(tooltip);

const std::string tooltip_id = tooltip->id();
DCHECK(!tooltip_popups_[tooltip_id]);
tooltip_popups_[tooltip_id] =
new brave_tooltips::BraveTooltipPopup(profile, std::move(tooltip));
if (!tooltip_popups_[tooltip_id]) {
tooltip_popups_[tooltip_id] =
new brave_tooltips::BraveTooltipPopup(profile, std::move(tooltip));
}
}

// static
Expand Down
17 changes: 17 additions & 0 deletions browser/ui/webui/brave_rewards/rewards_page_data_source.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <memory>

#include "base/feature_list.h"
#include "brave/components/brave_adaptive_captcha/server_util.h"
#include "brave/components/brave_rewards/common/features.h"
#include "brave/components/brave_rewards/resources/grit/brave_rewards_resources.h"
#include "brave/components/brave_rewards/resources/grit/rewards_page_generated_map.h"
Expand Down Expand Up @@ -138,6 +139,13 @@ static constexpr webui::LocalizedString kStrings[] = {
{"benefitsStoreText", IDS_REWARDS_BENEFITS_STORE_TEXT},
{"benefitsTitle", IDS_REWARDS_BENEFITS_TITLE},
{"cancelButtonLabel", IDS_REWARDS_PANEL_CANCEL},
{"captchaMaxAttemptsExceededText",
IDS_REWARDS_CAPTCHA_MAX_ATTEMPTS_EXCEEDED_TEXT},
{"captchaMaxAttemptsExceededTitle",
IDS_REWARDS_CAPTCHA_MAX_ATTEMPTS_EXCEEDED_TITLE},
{"captchaSolvedText", IDS_REWARDS_CAPTCHA_SOLVED_TEXT},
{"captchaSolvedTitle", IDS_REWARDS_CAPTCHA_SOLVED_TITLE},
{"captchaSupportButtonLabel", IDS_REWARDS_CAPTCHA_CONTACT_SUPPORT},
{"closeButtonLabel", IDS_BRAVE_REWARDS_ONBOARDING_CLOSE},
{"connectAccountSubtext", IDS_REWARDS_CONNECT_ACCOUNT_SUBTEXT},
{"connectAccountText", IDS_REWARDS_CONNECT_ACCOUNT_TEXT_2},
Expand Down Expand Up @@ -268,6 +276,15 @@ void CreateAndAddRewardsPageDataSource(content::WebUI& web_ui,
source, base::make_span(kRewardsPageGenerated, kRewardsPageGeneratedSize),
IDR_NEW_BRAVE_REWARDS_PAGE_HTML);

// Adaptive captcha challenges are displayed in an iframe on the Rewards
// panel. In order to display these challenges we need to specify in CSP that
// frames can be loaded from the adaptive captcha server URL.
source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::ChildSrc,
"frame-src 'self' " +
brave_adaptive_captcha::ServerUtil::GetInstance()->GetServerUrl("/") +
";");

// Override img-src to allow chrome://rewards-image support.
source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::ImgSrc,
Expand Down
78 changes: 68 additions & 10 deletions browser/ui/webui/brave_rewards/rewards_page_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/scoped_observation.h"
#include "brave/browser/brave_adaptive_captcha/brave_adaptive_captcha_service_factory.h"
#include "brave/browser/brave_ads/ads_service_factory.h"
#include "brave/browser/brave_rewards/rewards_service_factory.h"
#include "brave/components/brave_adaptive_captcha/brave_adaptive_captcha_service.h"
#include "brave/components/brave_ads/browser/ads_service.h"
#include "brave/components/brave_ads/core/public/ads_util.h"
#include "brave/components/brave_ads/core/public/history/ad_history_feature.h"
Expand All @@ -43,6 +45,8 @@ namespace brave_rewards {

namespace {

using brave_adaptive_captcha::BraveAdaptiveCaptchaServiceFactory;

static constexpr auto kPluralStrings =
base::MakeFixedFlatMap<std::string_view, int>(
{{"unconnectedAdsViewedText",
Expand Down Expand Up @@ -198,6 +202,8 @@ RewardsPageHandler::RewardsPageHandler(
bubble_delegate_(std::move(bubble_delegate)),
rewards_service_(RewardsServiceFactory::GetForProfile(profile)),
ads_service_(brave_ads::AdsServiceFactory::GetForProfile(profile)),
captcha_service_(
BraveAdaptiveCaptchaServiceFactory::GetForProfile(profile)),
prefs_(profile->GetPrefs()) {
CHECK(rewards_service_);
CHECK(ads_service_);
Expand Down Expand Up @@ -469,7 +475,38 @@ void RewardsPageHandler::GetAdsSettings(GetAdsSettingsCallback callback) {
}

void RewardsPageHandler::GetAdsStatement(GetAdsStatementCallback callback) {
ads_service_->GetStatementOfAccounts(std::move(callback));
auto on_statement = [](decltype(callback) callback,
brave_ads::mojom::StatementInfoPtr info) {
if (!info) {
std::move(callback).Run(nullptr);
return;
}

auto statement = mojom::AdsStatement::New();

statement->min_earnings_previous_month = info->min_earnings_previous_month;
statement->max_earnings_previous_month = info->max_earnings_previous_month;
statement->min_earnings_this_month = info->min_earnings_this_month;
statement->max_earnings_this_month = info->max_earnings_this_month;
statement->next_payment_date = info->next_payment_date;
statement->ads_received_this_month = info->ads_received_this_month;
statement->ad_type_summary_this_month = mojom::AdTypeSummary::New();

auto& summary = statement->ad_type_summary_this_month;
auto& ad_type_map = info->ads_summary_this_month;

using AdType = brave_ads::mojom::AdType;

summary->notification_ads = ad_type_map[AdType::kNotificationAd];
summary->new_tab_page_ads = ad_type_map[AdType::kNewTabPageAd];
summary->inline_content_ads = ad_type_map[AdType::kInlineContentAd];
summary->search_result_ads = ad_type_map[AdType::kSearchResultAd];

std::move(callback).Run(std::move(statement));
};

ads_service_->GetStatementOfAccounts(
base::BindOnce(on_statement, std::move(callback)));
}

void RewardsPageHandler::GetAdsHistory(GetAdsHistoryCallback callback) {
Expand Down Expand Up @@ -557,10 +594,8 @@ void RewardsPageHandler::ToggleAdLike(const std::string& history_item,
// reactions to use `mojom::ReactionInfo` instead of `AdHistoryItemInfo`.
const brave_ads::AdHistoryItemInfo ad_history_item =
brave_ads::AdHistoryItemFromValue(*dict);
brave_ads::mojom::ReactionInfoPtr mojom_reaction =
brave_ads::CreateReaction(ad_history_item);

ads_service_->ToggleLikeAd(std::move(mojom_reaction),
ads_service_->ToggleLikeAd(brave_ads::CreateReaction(ad_history_item),
base::IgnoreArgs<bool>(std::move(callback)));
}

Expand All @@ -576,10 +611,8 @@ void RewardsPageHandler::ToggleAdDislike(const std::string& history_item,
// reactions to use `mojom::ReactionInfo` instead of `AdHistoryItemInfo`.
const brave_ads::AdHistoryItemInfo ad_history_item =
brave_ads::AdHistoryItemFromValue(*dict);
brave_ads::mojom::ReactionInfoPtr mojom_reaction =
brave_ads::CreateReaction(ad_history_item);

ads_service_->ToggleDislikeAd(std::move(mojom_reaction),
ads_service_->ToggleDislikeAd(brave_ads::CreateReaction(ad_history_item),
base::IgnoreArgs<bool>(std::move(callback)));
}

Expand All @@ -596,11 +629,10 @@ void RewardsPageHandler::ToggleAdInappropriate(
// reactions to use `mojom::ReactionInfo` instead of `AdHistoryItemInfo`.
const brave_ads::AdHistoryItemInfo ad_history_item =
brave_ads::AdHistoryItemFromValue(*dict);
brave_ads::mojom::ReactionInfoPtr mojom_reaction =
brave_ads::CreateReaction(ad_history_item);

ads_service_->ToggleMarkAdAsInappropriate(
std::move(mojom_reaction), base::IgnoreArgs<bool>(std::move(callback)));
brave_ads::CreateReaction(ad_history_item),
base::IgnoreArgs<bool>(std::move(callback)));
}

void RewardsPageHandler::EnableRewards(const std::string& country_code,
Expand Down Expand Up @@ -629,6 +661,32 @@ void RewardsPageHandler::SendContribution(const std::string& creator_id,
std::move(callback));
}

void RewardsPageHandler::GetCaptchaInfo(GetCaptchaInfoCallback callback) {
if (!captcha_service_) {
std::move(callback).Run(nullptr);
return;
}

auto info = mojom::CaptchaInfo::New();
captcha_service_->GetScheduledCaptchaInfo(&info->url,
&info->max_attempts_exceeded);

if (info->url.empty()) {
info = nullptr;
}

std::move(callback).Run(std::move(info));
}

void RewardsPageHandler::OnCaptchaResult(bool success,
OnCaptchaResultCallback callback) {
if (captcha_service_) {
captcha_service_->UpdateScheduledCaptchaResult(success);
}
ads_service_->NotifyDidSolveAdaptiveCaptcha();
std::move(callback).Run();
}

void RewardsPageHandler::ResetRewards(ResetRewardsCallback callback) {
rewards_service_->CompleteReset(std::move(callback));
}
Expand Down
10 changes: 9 additions & 1 deletion browser/ui/webui/brave_rewards/rewards_page_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
class PrefService;
class Profile;

namespace brave_adaptive_captcha {
class BraveAdaptiveCaptchaService;
}

namespace brave_ads {
class AdsService;
}
Expand Down Expand Up @@ -93,7 +97,7 @@ class RewardsPageHandler : public mojom::RewardsPageHandler {
void GetAdsSettings(GetAdsSettingsCallback callback) override;
void GetAdsStatement(GetAdsStatementCallback callback) override;
void GetAdsHistory(GetAdsHistoryCallback callback) override;
void SetAdTypeEnabled(brave_ads::mojom::AdType mojom_ad_type,
void SetAdTypeEnabled(brave_ads::mojom::AdType ad_type,
bool enabled,
SetAdTypeEnabledCallback callback) override;
void SetNotificationAdsPerHour(
Expand All @@ -120,6 +124,8 @@ class RewardsPageHandler : public mojom::RewardsPageHandler {
double amount,
bool recurring,
SendContributionCallback callback) override;
void GetCaptchaInfo(GetCaptchaInfoCallback callback) override;
void OnCaptchaResult(bool success, OnCaptchaResultCallback callback) override;
void ResetRewards(ResetRewardsCallback callback) override;

private:
Expand All @@ -133,6 +139,8 @@ class RewardsPageHandler : public mojom::RewardsPageHandler {
std::unique_ptr<UpdateObserver> update_observer_;
raw_ptr<RewardsService> rewards_service_ = nullptr;
raw_ptr<brave_ads::AdsService> ads_service_ = nullptr;
raw_ptr<brave_adaptive_captcha::BraveAdaptiveCaptchaService>
captcha_service_ = nullptr;
raw_ptr<PrefService> prefs_ = nullptr;
};

Expand Down
36 changes: 35 additions & 1 deletion components/brave_rewards/common/mojom/rewards_page.mojom
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module brave_rewards.mojom;

import "brave/components/brave_ads/core/mojom/brave_ads.mojom";
import "brave/components/brave_rewards/common/mojom/rewards.mojom";
import "mojo/public/mojom/base/time.mojom";

// Initializes messaging between the browser and the page.
interface RewardsPageHandlerFactory {
Expand Down Expand Up @@ -51,6 +52,32 @@ struct AdsSettings {
array<AdsSubdivision> available_subdivisions;
};

struct AdTypeSummary {
int32 notification_ads;
int32 new_tab_page_ads;
int32 inline_content_ads;
int32 search_result_ads;
};

// We introduce a new struct instead of using `brave_ads.mojom.StatementInfo`
// directly, because the "ad type summary" map does not currently generate an
// appropriate TypeScript type, and the compiler can fail to detect breaking
// changes to that structure. Instead, we use named fields on `AdTypeSummary`.
struct AdsStatement {
double min_earnings_previous_month;
double max_earnings_previous_month;
double min_earnings_this_month;
double max_earnings_this_month;
mojo_base.mojom.Time next_payment_date;
int32 ads_received_this_month;
AdTypeSummary ad_type_summary_this_month;
};

struct CaptchaInfo {
string url;
bool max_attempts_exceeded;
};

// Browser-side handler for requests from WebUI page.
interface RewardsPageHandler {
// Notifies the browser that that Rewards page has rendered and is ready to be
Expand Down Expand Up @@ -123,7 +150,7 @@ interface RewardsPageHandler {
GetAdsSettings() => (AdsSettings settings);

// Returns information about the current state of ad views.
GetAdsStatement() => (brave_ads.mojom.StatementInfo? statement);
GetAdsStatement() => (AdsStatement? statement);

// Returns Ads history for the current user.
GetAdsHistory() => (string history);
Expand Down Expand Up @@ -157,6 +184,13 @@ interface RewardsPageHandler {
SendContribution(string creator_id, double amount, bool recurring)
=> (bool contribution_sent);

// Returns the current adaptive captcha info for the current user.
GetCaptchaInfo() => (CaptchaInfo? captchaInfo);

// Called when the user completes (with success or failure) an adaptive
// captcha attempt.
OnCaptchaResult(bool success) => ();

// Clears all Rewards-related user state.
ResetRewards() => (bool success);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* Copyright (c) 2024 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */

import * as React from 'react'

import { Optional } from '../../shared/lib/optional'

const balanceFormatter = new Intl.NumberFormat(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 4
})

interface Props {
balance: Optional<number>
}

export function AccountBalance(props: Props) {
if (!props.balance.hasValue()) {
return <></>
}
return <>
{balanceFormatter.format(props.balance.valueOr(0)) + ' BAT'}
</>
}
18 changes: 16 additions & 2 deletions components/brave_rewards/resources/rewards_page/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Onboarding } from './onboarding/onboarding'
import { OnboardingSuccessModal } from './onboarding/onboarding_success_modal'
import { ConnectAccount } from './connect_account'
import { AuthorizationModal } from './authorization_modal'
import { CaptchaModal } from './captcha_modal'
import { ContributeModal } from './contribute/contribute_modal'
import { ResetModal } from './reset_modal'
import { TosUpdateModal } from './tos_update_modal'
Expand All @@ -35,18 +36,21 @@ export function App() {
loading,
embedder,
paymentId,
tosUpdateRequired
tosUpdateRequired,
captchaInfo,
] = useAppState((state) => [
state.loading,
state.embedder,
state.paymentId,
state.tosUpdateRequired
state.tosUpdateRequired,
state.captchaInfo
])

const viewType = useBreakpoint()

const [showResetModal, setShowResetModal] = React.useState(false)
const [showContributeModal, setShowContributeModal] = React.useState(false)
const [hideCaptcha, setHideCaptcha] = React.useState(false)
const [showOnboardingSuccess, setShowOnboardingSuccess]
= React.useState(false)

Expand Down Expand Up @@ -127,6 +131,16 @@ export function App() {
return <ContributeModal onClose={() => setShowContributeModal(false)} />
}

if (captchaInfo && !hideCaptcha) {
return (
<CaptchaModal
captchaInfo={captchaInfo}
onCaptchaResult={(success) => model.onCaptchaResult(success)}
onClose={() => setHideCaptcha(true)}
/>
)
}

if (tosUpdateRequired) {
return (
<TosUpdateModal
Expand Down
Loading

0 comments on commit 59606b8

Please sign in to comment.