fxa_client/
device.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
5//! # Device Management
6//!
7//! Applications that connect to a user's account may register additional information
8//! about themselves via a "device record", which allows them to:
9//!
10//!    - customize how they appear in the user's account management page
11//!    - receive push notifications about events that happen on the account
12//!    - participate in the FxA "device commands" ecosystem
13//!
14//! For more details on FxA device registration and management, consult the
15//! [Firefox Accounts Device Registration docs](
16//! https://github.com/mozilla/fxa/blob/main/packages/fxa-auth-server/docs/device_registration.md).
17
18use error_support::handle_error;
19use serde::{Deserialize, Serialize};
20use sync15::DeviceType;
21
22use crate::{ApiResult, DevicePushSubscription, Error, FirefoxAccount};
23
24#[uniffi::export]
25impl FirefoxAccount {
26    /// Create a new device record for this application.
27    ///
28    /// **💾 This method alters the persisted account state.**
29    ///
30    /// This method register a device record for the application, providing basic metadata for
31    /// the device along with a list of supported [Device Capabilities](DeviceCapability) for
32    /// participating in the "device commands" ecosystem.
33    ///
34    /// Applications should call this method soon after a successful sign-in, to ensure
35    /// they they appear correctly in the user's account-management pages and when discovered
36    /// by other devices connected to the account.
37    ///
38    /// # Arguments
39    ///
40    ///    - `name` - human-readable display name to use for this application
41    ///    - `device_type` - the [type](DeviceType) of device the application is installed on
42    ///    - `supported_capabilities` - the set of [capabilities](DeviceCapability) to register
43    ///       for this device in the "device commands" ecosystem.
44    ///
45    /// # Notes
46    ///
47    ///    - Device registration is only available to applications that have been
48    ///      granted the `https://identity.mozilla.com/apps/oldsync` scope.
49    #[handle_error(Error)]
50    pub fn initialize_device(
51        &self,
52        name: &str,
53        device_type: DeviceType,
54        supported_capabilities: Vec<DeviceCapability>,
55    ) -> ApiResult<LocalDevice> {
56        // UniFFI doesn't have good handling of lists of references, work around it.
57        let supported_capabilities: Vec<_> = supported_capabilities.into_iter().collect();
58        self.internal
59            .lock()
60            .initialize_device(name, device_type, &supported_capabilities)
61    }
62
63    /// Get the device id registered for this application.
64    ///
65    /// # Notes
66    ///
67    ///    - If the application has not registered a device record, this method will
68    ///      throw an [`Other`](FxaError::Other) error.
69    ///        - (Yeah...sorry. This should be changed to do something better.)
70    ///    - Device metadata is only visible to applications that have been
71    ///      granted the `https://identity.mozilla.com/apps/oldsync` scope.
72    #[handle_error(Error)]
73    pub fn get_current_device_id(&self) -> ApiResult<String> {
74        self.internal.lock().get_current_device_id()
75    }
76
77    /// Get the list of devices registered on the user's account.
78    ///
79    /// **💾 This method alters the persisted account state.**
80    ///
81    /// This method returns a list of [`Device`] structs representing all the devices
82    /// currently attached to the user's account (including the current device).
83    /// The application might use this information to e.g. display a list of appropriate
84    /// send-tab targets.
85    ///
86    /// # Arguments
87    ///
88    ///    - `ignore_cache` - if true, always hit the server for fresh profile information.
89    ///
90    /// # Notes
91    ///
92    ///    - Device metadata is only visible to applications that have been
93    ///      granted the `https://identity.mozilla.com/apps/oldsync` scope.
94    #[handle_error(Error)]
95    pub fn get_devices(&self, ignore_cache: bool) -> ApiResult<Vec<Device>> {
96        self.internal
97            .lock()
98            .get_devices(ignore_cache)?
99            .into_iter()
100            .map(TryInto::try_into)
101            .collect::<Result<_, _>>()
102    }
103
104    /// Get the list of all client applications attached to the user's account.
105    ///
106    /// This method returns a list of [`AttachedClient`] structs representing all the applications
107    /// connected to the user's account. This includes applications that are registered as a device
108    /// as well as server-side services that the user has connected.
109    ///
110    /// It will only return active sessions.
111    /// For example, if a user has disconnected the service from their account,
112    /// it wouldn't appear in this list.
113    #[handle_error(Error)]
114    pub fn get_attached_clients(&self) -> ApiResult<Vec<AttachedClient>> {
115        self.internal
116            .lock()
117            .get_attached_clients()?
118            .into_iter()
119            .map(TryInto::try_into)
120            .collect::<Result<_, _>>()
121    }
122
123    /// Update the display name used for this application instance.
124    ///
125    /// **💾 This method alters the persisted account state.**
126    ///
127    /// This method modifies the name of the current application's device record, as seen by
128    /// other applications and in the user's account management pages.
129    ///
130    /// # Arguments
131    ///
132    ///    - `display_name` - the new name for the current device.
133    ///
134    /// # Notes
135    ///
136    ///    - Device registration is only available to applications that have been
137    ///      granted the `https://identity.mozilla.com/apps/oldsync` scope.
138    #[handle_error(Error)]
139    pub fn set_device_name(&self, display_name: &str) -> ApiResult<LocalDevice> {
140        self.internal.lock().set_device_name(display_name)
141    }
142
143    /// Clear any custom display name used for this application instance.
144    ///
145    /// **💾 This method alters the persisted account state.**
146    ///
147    /// This method clears the name of the current application's device record, causing other
148    /// applications or the user's account management pages to have to fill in some sort of
149    /// default name when displaying this device.
150    ///
151    /// # Notes
152    ///
153    ///    - Device registration is only available to applications that have been
154    ///      granted the `https://identity.mozilla.com/apps/oldsync` scope.
155    #[handle_error(Error)]
156    pub fn clear_device_name(&self) -> ApiResult<()> {
157        self.internal.lock().clear_device_name()
158    }
159
160    /// Ensure that the device record has a specific set of capabilities.
161    ///
162    /// **💾 This method alters the persisted account state.**
163    ///
164    /// This method checks that the currently-registered device record is advertising the
165    /// given set of capabilities in the FxA "device commands" ecosystem. If not, then it
166    /// updates the device record to do so.
167    ///
168    /// Applications should call this method on each startup as a way to ensure that their
169    /// expected set of capabilities is being accurately reflected on the FxA server, and
170    /// to handle the rollout of new capabilities over time.
171    ///
172    /// # Arguments
173    ///
174    ///    - `supported_capabilities` - the set of [capabilities](DeviceCapability) to register
175    ///       for this device in the "device commands" ecosystem.
176    ///
177    /// # Notes
178    ///
179    ///    - Device registration is only available to applications that have been
180    ///      granted the `https://identity.mozilla.com/apps/oldsync` scope.
181    #[handle_error(Error)]
182    pub fn ensure_capabilities(
183        &self,
184        supported_capabilities: Vec<DeviceCapability>,
185    ) -> ApiResult<LocalDevice> {
186        let supported_capabilities: Vec<_> = supported_capabilities.into_iter().collect();
187        self.internal
188            .lock()
189            .ensure_capabilities(&supported_capabilities)
190    }
191}
192
193#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
194/// Device configuration
195pub struct DeviceConfig {
196    pub name: String,
197    pub device_type: sync15::DeviceType,
198    pub capabilities: Vec<DeviceCapability>,
199}
200
201#[derive(uniffi::Record, Debug, Clone, Serialize, Deserialize)]
202/// Local device that's connecting to FxA
203///
204/// This is returned by the device update methods and represents the server's view of the local
205/// device.
206pub struct LocalDevice {
207    pub id: String,
208    pub display_name: String,
209    pub device_type: sync15::DeviceType,
210    pub capabilities: Vec<DeviceCapability>,
211    pub push_subscription: Option<DevicePushSubscription>,
212    pub push_endpoint_expired: bool,
213}
214
215#[derive(uniffi::Record, Debug)]
216/// A device connected to the user's account.
217///
218/// This struct provides metadata about a device connected to the user's account.
219/// This data would typically be used to display e.g. the list of candidate devices
220/// in a "send tab" menu.
221pub struct Device {
222    pub id: String,
223    pub display_name: String,
224    pub device_type: sync15::DeviceType,
225    pub capabilities: Vec<DeviceCapability>,
226    pub push_subscription: Option<DevicePushSubscription>,
227    pub push_endpoint_expired: bool,
228    pub is_current_device: bool,
229    pub last_access_time: Option<i64>,
230}
231
232#[derive(uniffi::Enum, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
233/// A "capability" offered by a device.
234///
235/// In the FxA ecosystem, connected devices may advertise their ability to respond
236/// to various "commands" that can be invoked by other devices. The details of
237/// executing these commands are encapsulated as part of the FxA Client component,
238/// so consumers simply need to select which ones they want to support, and can
239/// use the variants of this enum to do so.
240pub enum DeviceCapability {
241    SendTab,
242    CloseTabs,
243}
244
245#[derive(uniffi::Record)]
246/// A client connected to the user's account.
247///
248/// This struct provides metadata about a client connected to the user's account.
249/// Unlike the [`Device`] struct, "clients" encompasses both client-side and server-side
250/// applications - basically anything where the user is able to sign in with their
251/// Firefox Account.
252///
253///
254/// This data would typically be used for targeted messaging purposes, catering the
255/// contents of the message to what other applications the user has on their account.
256pub struct AttachedClient {
257    pub client_id: Option<String>,
258    pub device_id: Option<String>,
259    pub device_type: DeviceType,
260    pub is_current_session: bool,
261    pub name: Option<String>,
262    pub created_time: Option<i64>,
263    pub last_access_time: Option<i64>,
264    pub scope: Option<Vec<String>>,
265}
266
267#[derive(uniffi::Enum, Clone, Debug, PartialEq, Eq)]
268/// The result of invoking a "close tabs" command.
269///
270/// If [`FirefoxAccount::close_tabs`] is called with more URLs than can fit
271/// into a single command payload, the URLs will be chunked and sent in
272/// multiple commands.
273///
274/// Chunking breaks the atomicity of a "close tabs" command, but
275/// reduces the number of these commands that FxA sends to other devices.
276/// This is critical for platforms like iOS, where every command triggers a
277/// push message that must show a user-visible notification.
278pub enum CloseTabsResult {
279    /// All URLs passed to [`FirefoxAccount::close_tabs`] were chunked and sent
280    /// in one or more device commands.
281    Ok,
282    /// One or more URLs passed to [`FirefoxAccount::close_tabs`] couldn't be sent
283    /// in a device command. The caller can assume that:
284    ///
285    /// 1. Any URL in the returned list of `urls` was not sent, and
286    ///    should be retried.
287    /// 2. All other URLs that were passed to [`FirefoxAccount::close_tabs`], and
288    ///    that are _not_ in the list of `urls`, were chunked and sent.
289    TabsNotClosed { urls: Vec<String> },
290}