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}