Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

### Ads Client
- Added `rotation_days` parameter to `MozAdsClientBuilder` to allow embedders to configure the context ID rotation period. ([#7262](https://github.com/mozilla/application-services/pull/7262))
- Added `MozAdsContextIdProvider` callback interface and `context_id_provider()` builder method, allowing embedders (e.g. HNT) to supply an externally managed context ID. When a provider is set the embedded `ContextIDComponent` is not created. Mobile consumers that do not set a provider are unaffected. ([AC-95](https://mozilla-hub.atlassian.net/browse/AC-95))

### Logins
- **BREAKING**: Removed `time_of_last_breach` field from `LoginMeta` and `Login`. This can be derived from Remote Settings during runtime instead.
Expand Down
68 changes: 49 additions & 19 deletions components/ads-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,32 +27,56 @@ const DEFAULT_TTL_SECONDS: u64 = 300;
const DEFAULT_MAX_CACHE_SIZE_MIB: u64 = 10;
const DEFAULT_ROTATION_DAYS: u8 = 3;

/// Pure Rust trait for providing a context ID to the ads client.
/// Implement this to supply an externally managed context ID (e.g. from HNT).
pub trait ContextIdProvider: Send + Sync {
fn context_id(&self) -> String;
}

/// Determines how the ads client obtains a context ID.
enum ContextIdSource {
/// Use the embedded `ContextIDComponent` (default, mobile-compatible behaviour).
Embedded {
component: ContextIDComponent,
rotation_days: u8,
},
/// Delegate to an external provider; rotation and deletion are the caller's responsibility.
External(Box<dyn ContextIdProvider>),
}

pub struct AdsClient<T>
where
T: Clone + Telemetry,
{
client: MARSClient<T>,
context_id_component: ContextIDComponent,
context_id_source: ContextIdSource,
environment: Environment,
telemetry: T,
rotation_days: u8,
}

impl<T> AdsClient<T>
where
T: Clone + Telemetry,
{
pub fn new(client_config: AdsClientConfig<T>) -> Self {
let context_id = Uuid::new_v4().to_string();
let context_id_component = ContextIDComponent::new(
&context_id,
0,
cfg!(test),
Box::new(DefaultContextIdCallback),
);
let context_id_source = if let Some(provider) = client_config.context_id_provider {
ContextIdSource::External(provider)
} else {
let context_id = Uuid::new_v4().to_string();
let component = ContextIDComponent::new(
&context_id,
0,
cfg!(test),
Box::new(DefaultContextIdCallback),
);
ContextIdSource::Embedded {
component,
rotation_days: DEFAULT_ROTATION_DAYS,
}
};

let telemetry = client_config.telemetry;
let environment = client_config.environment;
let rotation_days = client_config.rotation_days.unwrap_or(DEFAULT_ROTATION_DAYS);

// Configure the cache if a path is provided.
// Defaults for ttl and cache size are also set if unspecified.
Expand Down Expand Up @@ -80,10 +104,9 @@ where
let client = MARSClient::new(http_cache, telemetry.clone());
let client = Self {
environment,
context_id_component,
context_id_source,
client,
telemetry: telemetry.clone(),
rotation_days,
};
telemetry.record(&ClientOperationEvent::New);
return client;
Expand All @@ -92,10 +115,9 @@ where
let client = MARSClient::new(None, telemetry.clone());
let client = Self {
environment,
context_id_component,
context_id_source,
client,
telemetry: telemetry.clone(),
rotation_days,
};
telemetry.record(&ClientOperationEvent::New);
client
Expand Down Expand Up @@ -211,7 +233,13 @@ where
}

pub fn get_context_id(&self) -> context_id::ApiResult<String> {
self.context_id_component.request(self.rotation_days)
match &self.context_id_source {
ContextIdSource::Embedded {
component,
rotation_days,
} => component.request(*rotation_days),
ContextIdSource::External(provider) => Ok(provider.context_id()),
}
}

pub fn clear_cache(&self) -> Result<(), HttpCacheError> {
Expand Down Expand Up @@ -243,18 +271,20 @@ mod tests {
fn new_with_mars_client(
client: MARSClient<MozAdsTelemetryWrapper>,
) -> AdsClient<MozAdsTelemetryWrapper> {
let context_id_component = ContextIDComponent::new(
let component = ContextIDComponent::new(
&uuid::Uuid::new_v4().to_string(),
0,
false,
Box::new(DefaultContextIdCallback),
);
AdsClient {
environment: Environment::Test,
context_id_component,
context_id_source: ContextIdSource::Embedded {
component,
rotation_days: DEFAULT_ROTATION_DAYS,
},
client,
telemetry: MozAdsTelemetryWrapper::noop(),
rotation_days: DEFAULT_ROTATION_DAYS,
}
}

Expand All @@ -264,7 +294,7 @@ mod tests {
environment: Environment::Test,
cache_config: None,
telemetry: MozAdsTelemetryWrapper::noop(),
rotation_days: None,
context_id_provider: None,
};
let client = AdsClient::new(config);
let context_id = client.get_context_id().unwrap();
Expand Down
4 changes: 3 additions & 1 deletion components/ads-client/src/client/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ where
pub environment: Environment,
pub cache_config: Option<AdsCacheConfig>,
pub telemetry: T,
pub rotation_days: Option<u8>,
/// Optional external context ID provider. When `Some`, the embedded
/// `ContextIDComponent` is not created.
pub context_id_provider: Option<Box<dyn super::ContextIdProvider>>,
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
Expand Down
30 changes: 25 additions & 5 deletions components/ads-client/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::client::ad_response::{
AdCallbacks, AdImage, AdSpoc, AdTile, SpocFrequencyCaps, SpocRanking,
};
use crate::client::config::{AdsCacheConfig, AdsClientConfig, Environment};
use crate::client::AdsClient;
use crate::client::{AdsClient, ContextIdProvider};
use crate::error::ComponentError;
use crate::ffi::telemetry::MozAdsTelemetryWrapper;
use crate::http_cache::{CacheMode, RequestCachePolicy};
Expand Down Expand Up @@ -49,6 +49,19 @@ impl GetErrorHandling for ComponentError {
}
}

#[uniffi::export(with_foreign)]
pub trait MozAdsContextIdProvider: Send + Sync {
fn context_id(&self) -> String;
}

struct MozAdsContextIdProviderWrapper(Arc<dyn MozAdsContextIdProvider>);

impl ContextIdProvider for MozAdsContextIdProviderWrapper {
fn context_id(&self) -> String {
self.0.context_id()
}
}

#[derive(uniffi::Record)]
pub struct MozAdsRequestOptions {
pub cache_policy: Option<MozAdsRequestCachePolicy>,
Expand Down Expand Up @@ -99,7 +112,7 @@ struct MozAdsClientBuilderInner {
environment: Option<MozAdsEnvironment>,
cache_config: Option<MozAdsCacheConfig>,
telemetry: Option<Arc<dyn MozAdsTelemetry>>,
rotation_days: Option<u8>,
context_id_provider: Option<Arc<dyn MozAdsContextIdProvider>>,
}

impl Default for MozAdsClientBuilder {
Expand Down Expand Up @@ -130,13 +143,20 @@ impl MozAdsClientBuilder {
self
}

pub fn rotation_days(self: Arc<Self>, rotation_days: u8) -> Arc<Self> {
self.0.lock().rotation_days = Some(rotation_days);
pub fn context_id_provider(
self: Arc<Self>,
provider: Arc<dyn MozAdsContextIdProvider>,
) -> Arc<Self> {
self.0.lock().context_id_provider = Some(provider);
self
}

pub fn build(&self) -> MozAdsClient {
let inner = self.0.lock();
let context_id_provider = inner
.context_id_provider
.clone()
.map(|p| Box::new(MozAdsContextIdProviderWrapper(p)) as Box<dyn ContextIdProvider>);
let client_config = AdsClientConfig {
environment: inner.environment.unwrap_or_default().into(),
cache_config: inner.cache_config.clone().map(Into::into),
Expand All @@ -145,7 +165,7 @@ impl MozAdsClientBuilder {
.clone()
.map(MozAdsTelemetryWrapper::new)
.unwrap_or_else(MozAdsTelemetryWrapper::noop),
rotation_days: inner.rotation_days,
context_id_provider,
};
let client = AdsClient::new(client_config);
MozAdsClient {
Expand Down
Loading