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}