remote_settings/
lib.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use std::{collections::HashMap, sync::Arc};
6
7use error_support::{convert_log_report_error, handle_error};
8
9pub mod client;
10pub mod config;
11pub mod context;
12pub mod error;
13pub mod schema;
14pub mod service;
15#[cfg(feature = "signatures")]
16pub(crate) mod signatures;
17pub mod storage;
18pub mod telemetry;
19
20pub(crate) mod jexl_filter;
21mod macros;
22
23pub use client::{Attachment, RemoteSettingsRecord, RsJsonObject};
24pub use config::{BaseUrl, RemoteSettingsConfig, RemoteSettingsServer};
25pub use context::RemoteSettingsContext;
26pub use error::{trace, ApiResult, RemoteSettingsError, Result};
27pub use telemetry::{RemoteSettingsTelemetry, SyncStatus, UptakeEventExtras};
28
29use error::Error;
30use storage::Storage;
31use telemetry::RemoteSettingsTelemetryWrapper;
32
33uniffi::setup_scaffolding!("remote_settings");
34
35/// Application-level Remote Settings manager.
36///
37/// This handles application-level operations, like syncing all the collections, and acts as a
38/// factory for creating clients.
39#[derive(uniffi::Object)]
40pub struct RemoteSettingsService {
41    // This struct adapts server::RemoteSettingsService into the public API
42    internal: service::RemoteSettingsService,
43}
44
45#[uniffi::export]
46impl RemoteSettingsService {
47    /// Construct a [RemoteSettingsService]
48    ///
49    /// This is typically done early in the application-startup process.
50    ///
51    /// This method performs no IO or network requests and is safe to run in a main thread that
52    /// can't be blocked.
53    ///
54    /// `storage_dir` is a directory to store SQLite files in -- one per collection. If the
55    /// directory does not exist, it will be created when the storage is first used. Only the
56    /// directory and the SQLite files will be created, any parent directories must already exist.
57    #[uniffi::constructor]
58    pub fn new(storage_dir: String, config: RemoteSettingsConfig) -> Self {
59        Self {
60            internal: service::RemoteSettingsService::new(storage_dir, config),
61        }
62    }
63
64    /// Set the telemetry implementation used to record Glean metrics.
65    /// This should be set to a real implementation (eg. Kotlin, Swift).
66    /// If not set, all metric recording is a no-op.
67    pub fn set_telemetry(&self, telemetry: Arc<dyn RemoteSettingsTelemetry>) {
68        self.internal
69            .set_telemetry(RemoteSettingsTelemetryWrapper::new(telemetry));
70    }
71
72    /// Create a new Remote Settings client
73    ///
74    /// This method performs no IO or network requests and is safe to run in a main thread that can't be blocked.
75    pub fn make_client(&self, collection_name: String) -> Arc<RemoteSettingsClient> {
76        self.internal.make_client(collection_name)
77    }
78
79    /// Sync collections for all active clients
80    ///
81    /// The returned list is the list of collections for which updates were seen
82    /// and then synced.
83    #[handle_error(Error)]
84    pub fn sync(&self) -> ApiResult<Vec<String>> {
85        self.internal.sync()
86    }
87
88    /// Update the remote settings config
89    ///
90    /// This will cause all current and future clients to use new config and will delete any stored
91    /// records causing the clients to return new results from the new config.
92    ///
93    /// Only intended for QA/debugging.  Swapping the remote settings server in the middle of
94    /// execution can cause weird effects.
95    #[handle_error(Error)]
96    pub fn update_config(&self, config: RemoteSettingsConfig) -> ApiResult<()> {
97        self.internal.update_config(config)
98    }
99
100    pub fn client_url(&self) -> String {
101        self.internal.client_url().to_string()
102    }
103}
104
105/// Client for a single Remote Settings collection
106///
107/// Use [RemoteSettingsService::make_client] to create these.
108#[derive(uniffi::Object)]
109pub struct RemoteSettingsClient {
110    // This struct adapts client::RemoteSettingsClient into the public API
111    internal: client::RemoteSettingsClient,
112}
113
114#[uniffi::export]
115impl RemoteSettingsClient {
116    /// Collection this client is for
117    pub fn collection_name(&self) -> String {
118        self.internal.collection_name().to_owned()
119    }
120
121    /// Get the current set of records.
122    ///
123    /// This method normally fetches records from the last sync.  This means that it returns fast
124    /// and does not make any network requests.
125    ///
126    /// If records have not yet been synced it will return None.  Use `sync_if_empty = true` to
127    /// change this behavior and perform a network request in this case.  That this is probably a
128    /// bad idea if you want to fetch the setting in application startup or when building the UI.
129    ///
130    /// None will also be returned on disk IO errors or other unexpected errors.  The reason for
131    /// this is that there is not much an application can do in this situation other than fall back
132    /// to the same default handling as if records have not been synced.
133    ///
134    /// Application-services schedules regular dumps of the server data for specific collections.
135    /// For these collections, `get_records` will never return None.  If you would like to add your
136    /// collection to this list, please reach out to the DISCO team.
137    #[uniffi::method(default(sync_if_empty = false))]
138    pub fn get_records(&self, sync_if_empty: bool) -> Option<Vec<RemoteSettingsRecord>> {
139        match self.internal.get_records(sync_if_empty) {
140            Ok(records) => records,
141            Err(e) => {
142                // Log/report the error
143                trace!("get_records error: {e}");
144                convert_log_report_error(e);
145                // Throw away the converted result and return None, there's nothing a client can
146                // really do with an error except treat it as the None case
147                None
148            }
149        }
150    }
151
152    /// Get the current set of records as a map of record_id -> record.
153    ///
154    /// See [Self::get_records] for an explanation of when this makes network requests, error
155    /// handling, and how the `sync_if_empty` param works.
156    #[uniffi::method(default(sync_if_empty = false))]
157    pub fn get_records_map(
158        &self,
159        sync_if_empty: bool,
160    ) -> Option<HashMap<String, RemoteSettingsRecord>> {
161        self.get_records(sync_if_empty)
162            .map(|records| records.into_iter().map(|r| (r.id.clone(), r)).collect())
163    }
164
165    /// Returns the last_modified value for the collection as an unsigned int64.
166    #[uniffi::method()]
167    pub fn get_last_modified_timestamp(&self) -> Option<u64> {
168        self.internal
169            .get_last_modified_timestamp()
170            .unwrap_or_default()
171    }
172
173    /// Get attachment data for a remote settings record
174    ///
175    /// Attachments are large binary blobs used for data that doesn't fit in a normal record.  They
176    /// are handled differently than other record data:
177    ///
178    ///   - Attachments are not downloaded in [RemoteSettingsService::sync]
179    ///   - This method will make network requests if the attachment is not cached
180    ///   - This method will throw if there is a network or other error when fetching the
181    ///     attachment data.
182    #[handle_error(Error)]
183    pub fn get_attachment(&self, record: &RemoteSettingsRecord) -> ApiResult<Vec<u8>> {
184        self.internal.get_attachment(record)
185    }
186
187    #[handle_error(Error)]
188    pub fn sync(&self) -> ApiResult<()> {
189        self.internal.sync()
190    }
191
192    #[handle_error(Error)]
193    pub fn reset_storage(&self) -> ApiResult<()> {
194        self.internal.reset_storage()
195    }
196
197    /// Shutdown the client, releasing the SQLite connection used to cache records.
198    pub fn shutdown(&self) {
199        self.internal.shutdown()
200    }
201}
202
203impl RemoteSettingsClient {
204    /// Create a new client.  This is not exposed to foreign code, consumers need to call
205    /// [RemoteSettingsService::make_client]
206    fn new(
207        base_url: BaseUrl,
208        bucket_name: String,
209        collection_name: String,
210        #[allow(unused)] context: Option<RemoteSettingsContext>,
211        storage: Storage,
212    ) -> Self {
213        Self {
214            internal: client::RemoteSettingsClient::new(
215                base_url,
216                bucket_name,
217                collection_name,
218                context,
219                storage,
220            ),
221        }
222    }
223}