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    /// Create a new Remote Settings client
65    ///
66    /// This method performs no IO or network requests and is safe to run in a main thread that can't be blocked.
67    pub fn make_client(&self, collection_name: String) -> Arc<RemoteSettingsClient> {
68        self.internal.make_client(collection_name)
69    }
70
71    /// Sync collections for all active clients
72    ///
73    /// The returned list is the list of collections for which updates were seen
74    /// and then synced.
75    #[handle_error(Error)]
76    pub fn sync(&self) -> ApiResult<Vec<String>> {
77        self.internal.sync()
78    }
79
80    /// Update the remote settings config
81    ///
82    /// This will cause all current and future clients to use new config and will delete any stored
83    /// records causing the clients to return new results from the new config.
84    ///
85    /// Only intended for QA/debugging.  Swapping the remote settings server in the middle of
86    /// execution can cause weird effects.
87    #[handle_error(Error)]
88    pub fn update_config(&self, config: RemoteSettingsConfig) -> ApiResult<()> {
89        self.internal.update_config(config)
90    }
91
92    pub fn client_url(&self) -> String {
93        self.internal.client_url().to_string()
94    }
95}
96
97#[cfg_attr(feature = "telemetry-submission", uniffi::export)]
98impl RemoteSettingsService {
99    /// Set the telemetry implementation used to record Glean metrics.
100    /// This should be set to a real implementation (eg. Kotlin, Swift).
101    /// If not set, all metric recording is a no-op.
102    pub fn set_telemetry(&self, telemetry: Arc<dyn RemoteSettingsTelemetry>) {
103        self.internal
104            .set_telemetry(RemoteSettingsTelemetryWrapper::new(telemetry));
105    }
106}
107
108/// Client for a single Remote Settings collection
109///
110/// Use [RemoteSettingsService::make_client] to create these.
111#[derive(uniffi::Object)]
112pub struct RemoteSettingsClient {
113    // This struct adapts client::RemoteSettingsClient into the public API
114    internal: client::RemoteSettingsClient,
115}
116
117#[uniffi::export]
118impl RemoteSettingsClient {
119    /// Collection this client is for
120    pub fn collection_name(&self) -> String {
121        self.internal.collection_name().to_owned()
122    }
123
124    /// Get the current set of records.
125    ///
126    /// This method normally fetches records from the last sync.  This means that it returns fast
127    /// and does not make any network requests.
128    ///
129    /// If records have not yet been synced it will return None.  Use `sync_if_empty = true` to
130    /// change this behavior and perform a network request in this case.  That this is probably a
131    /// bad idea if you want to fetch the setting in application startup or when building the UI.
132    ///
133    /// None will also be returned on disk IO errors or other unexpected errors.  The reason for
134    /// this is that there is not much an application can do in this situation other than fall back
135    /// to the same default handling as if records have not been synced.
136    ///
137    /// Application-services schedules regular dumps of the server data for specific collections.
138    /// For these collections, `get_records` will never return None.  If you would like to add your
139    /// collection to this list, please reach out to the DISCO team.
140    #[uniffi::method(default(sync_if_empty = false))]
141    pub fn get_records(&self, sync_if_empty: bool) -> Option<Vec<RemoteSettingsRecord>> {
142        match self.internal.get_records(sync_if_empty) {
143            Ok(records) => records,
144            Err(e) => {
145                // Log/report the error
146                trace!("get_records error: {e}");
147                convert_log_report_error(e);
148                // Throw away the converted result and return None, there's nothing a client can
149                // really do with an error except treat it as the None case
150                None
151            }
152        }
153    }
154
155    /// Get the current set of records as a map of record_id -> record.
156    ///
157    /// See [Self::get_records] for an explanation of when this makes network requests, error
158    /// handling, and how the `sync_if_empty` param works.
159    #[uniffi::method(default(sync_if_empty = false))]
160    pub fn get_records_map(
161        &self,
162        sync_if_empty: bool,
163    ) -> Option<HashMap<String, RemoteSettingsRecord>> {
164        self.get_records(sync_if_empty)
165            .map(|records| records.into_iter().map(|r| (r.id.clone(), r)).collect())
166    }
167
168    /// Returns the last_modified value for the collection as an unsigned int64.
169    #[uniffi::method()]
170    pub fn get_last_modified_timestamp(&self) -> Option<u64> {
171        self.internal
172            .get_last_modified_timestamp()
173            .unwrap_or_default()
174    }
175
176    /// Get attachment data for a remote settings record
177    ///
178    /// Attachments are large binary blobs used for data that doesn't fit in a normal record.  They
179    /// are handled differently than other record data:
180    ///
181    ///   - Attachments are not downloaded in [RemoteSettingsService::sync]
182    ///   - This method will make network requests if the attachment is not cached
183    ///   - This method will throw if there is a network or other error when fetching the
184    ///     attachment data.
185    #[handle_error(Error)]
186    pub fn get_attachment(&self, record: &RemoteSettingsRecord) -> ApiResult<Vec<u8>> {
187        self.internal.get_attachment(record)
188    }
189
190    #[handle_error(Error)]
191    pub fn sync(&self) -> ApiResult<()> {
192        self.internal.sync()
193    }
194
195    #[handle_error(Error)]
196    pub fn reset_storage(&self) -> ApiResult<()> {
197        self.internal.reset_storage()
198    }
199
200    /// Shutdown the client, releasing the SQLite connection used to cache records.
201    pub fn shutdown(&self) {
202        self.internal.shutdown()
203    }
204}
205
206impl RemoteSettingsClient {
207    /// Create a new client.  This is not exposed to foreign code, consumers need to call
208    /// [RemoteSettingsService::make_client]
209    fn new(
210        base_url: BaseUrl,
211        bucket_name: String,
212        collection_name: String,
213        #[allow(unused)] context: Option<RemoteSettingsContext>,
214        storage: Storage,
215    ) -> Self {
216        Self {
217            internal: client::RemoteSettingsClient::new(
218                base_url,
219                bucket_name,
220                collection_name,
221                context,
222                storage,
223            ),
224        }
225    }
226}