fxa_client/
push.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 error_support::handle_error;
6use serde::{Deserialize, Serialize};
7
8use crate::{internal, ApiResult, CloseTabsResult, Device, Error, FirefoxAccount, LocalDevice};
9
10#[uniffi::export]
11impl FirefoxAccount {
12    /// Set or update a push subscription endpoint for this device.
13    ///
14    /// **💾 This method alters the persisted account state.**
15    ///
16    /// This method registers the given webpush subscription with the FxA server, requesting
17    /// that is send notifications in the event of any significant changes to the user's
18    /// account. When the application receives a push message at the registered subscription
19    /// endpoint, it should decrypt the payload and pass it to the [`handle_push_message`](
20    /// FirefoxAccount::handle_push_message) method for processing.
21    ///
22    /// # Arguments
23    ///
24    ///    - `subscription` - the [`DevicePushSubscription`] details to register with the server.
25    ///
26    /// # Notes
27    ///
28    ///    - Device registration is only available to applications that have been
29    ///      granted the `https://identity.mozilla.com/apps/oldsync` scope.
30    #[handle_error(Error)]
31    pub fn set_push_subscription(
32        &self,
33        subscription: DevicePushSubscription,
34    ) -> ApiResult<LocalDevice> {
35        self.internal
36            .lock()
37            .set_push_subscription(subscription.into())
38    }
39
40    /// Process and respond to a server-delivered account update message
41    ///
42    /// **💾 This method alters the persisted account state.**
43    ///
44    /// Applications should call this method whenever they receive a push notification from the Firefox Accounts server.
45    /// Such messages typically indicate a noteworthy change of state on the user's account, such as an update to their profile information
46    /// or the disconnection of a client. The [`FirefoxAccount`] struct will update its internal state
47    /// accordingly and return an individual [`AccountEvent`] struct describing the event, which the application
48    /// may use for further processing.
49    ///
50    /// It's important to note if the event is [`AccountEvent::CommandReceived`], the caller should call
51    /// [`FirefoxAccount::poll_device_commands`]
52    #[handle_error(Error)]
53    pub fn handle_push_message(&self, payload: &str) -> ApiResult<AccountEvent> {
54        self.internal.lock().handle_push_message(payload)
55    }
56
57    /// Poll the server for any pending device commands.
58    ///
59    /// **💾 This method alters the persisted account state.**
60    ///
61    /// Applications that have registered one or more [`DeviceCapability`]s with the server can use
62    /// this method to check whether other devices on the account have sent them any commands.
63    /// It will return a list of [`IncomingDeviceCommand`] structs for the application to process.
64    ///
65    /// # Notes
66    ///
67    ///    - Device commands are typically delivered via push message and the [`CommandReceived`](
68    ///      AccountEvent::CommandReceived) event. Polling should only be used as a backup delivery
69    ///      mechanism, f the application has reason to believe that push messages may have been missed.
70    ///    - Device commands functionality is only available to applications that have been
71    ///      granted the `https://identity.mozilla.com/apps/oldsync` scope.
72    #[handle_error(Error)]
73    pub fn poll_device_commands(&self) -> ApiResult<Vec<IncomingDeviceCommand>> {
74        self.internal
75            .lock()
76            .poll_device_commands(internal::device::CommandFetchReason::Poll)?
77            .into_iter()
78            .map(TryFrom::try_from)
79            .collect::<Result<_, _>>()
80    }
81
82    /// Use device commands to send a single tab to another device.
83    ///
84    /// **💾 This method alters the persisted account state.**
85    ///
86    /// If a device on the account has registered the [`SendTab`](DeviceCapability::SendTab)
87    /// capability, this method can be used to send it a tab.
88    ///
89    /// # Notes
90    ///
91    ///    - If the given device id does not existing or is not capable of receiving tabs,
92    ///      this method will throw an [`Other`](FxaError::Other) error.
93    ///        - (Yeah...sorry. This should be changed to do something better.)
94    ///    - It is not currently possible to send a full [`SendTabPayload`] to another device,
95    ///      but that's purely an API limitation that should go away in future.
96    ///    - Device commands functionality is only available to applications that have been
97    ///      granted the `https://identity.mozilla.com/apps/oldsync` scope.
98    #[handle_error(Error)]
99    #[uniffi::method(default(is_private = false))]
100    pub fn send_single_tab(
101        &self,
102        target_device_id: &str,
103        title: &str,
104        url: &str,
105        is_private: bool,
106    ) -> ApiResult<()> {
107        self.internal
108            .lock()
109            .send_single_tab(target_device_id, title, url, is_private)
110    }
111
112    /// Use device commands to close one or more tabs on another device.
113    ///
114    /// **💾 This method alters the persisted account state.**
115    ///
116    /// If a device on the account has registered the [`CloseTabs`](DeviceCapability::CloseTabs)
117    /// capability, this method can be used to close its tabs.
118    #[handle_error(Error)]
119    pub fn close_tabs(
120        &self,
121        target_device_id: &str,
122        urls: Vec<String>,
123    ) -> ApiResult<CloseTabsResult> {
124        self.internal.lock().close_tabs(target_device_id, urls)
125    }
126}
127
128#[derive(uniffi::Record, Debug, Clone, Serialize, Deserialize)]
129/// Details of a web-push subscription endpoint.
130///
131/// This struct encapsulates the details of a web-push subscription endpoint,
132/// including all the information necessary to send a notification to its owner.
133/// Devices attached to the user's account may register one of these in order
134/// to receive timely updates about account-related events.
135///
136/// Managing a web-push subscription is outside of the scope of this component.
137///
138pub struct DevicePushSubscription {
139    pub endpoint: String,
140    pub public_key: String,
141    pub auth_key: String,
142}
143
144#[allow(clippy::large_enum_variant)]
145#[derive(uniffi::Enum, Debug)]
146/// An event that happened on the user's account.
147///
148/// If the application has registered a [`DevicePushSubscription`] as part of its
149/// device record, then the Firefox Accounts server can send push notifications
150/// about important events that happen on the user's account. This enum represents
151/// the different kinds of event that can occur.
152///
153// Clippy suggests we Box<> the CommandReceiver variant here,
154// but UniFFI isn't able to look through boxes yet, so we
155// disable the warning.
156pub enum AccountEvent {
157    /// Sent when another device has invoked a command for this device to execute.
158    ///
159    /// When receiving this event, the application should inspect the contained
160    /// command and react appropriately.
161    CommandReceived { command: IncomingDeviceCommand },
162    /// Sent when the user has modified their account profile information.
163    ///
164    /// When receiving this event, the application should request fresh profile
165    /// information by calling [`get_profile`](FirefoxAccount::get_profile) with
166    /// `ignore_cache` set to true, and update any profile information displayed
167    /// in its UI.
168    ///
169    ProfileUpdated,
170    /// Sent when when there has been a change in authorization status.
171    ///
172    /// When receiving this event, the application should check whether it is
173    /// still connected to the user's account by calling [`check_authorization_status`](
174    /// FirefoxAccount::check_authorization_status), and updating its UI as appropriate.
175    ///
176    AccountAuthStateChanged,
177    /// Sent when the user deletes their Firefox Account.
178    ///
179    /// When receiving this event, the application should act as though the user had
180    /// signed out, discarding any persisted account state.
181    AccountDestroyed,
182    /// Sent when a new device connects to the user's account.
183    ///
184    /// When receiving this event, the application may use it to trigger an update
185    /// of any UI that shows the list of connected devices. It may also show the
186    /// user an informational notice about the new device, as a security measure.
187    DeviceConnected { device_name: String },
188    /// Sent when a device disconnects from the user's account.
189    ///
190    /// When receiving this event, the application may use it to trigger an update
191    /// of any UI that shows the list of connected devices.
192    DeviceDisconnected {
193        device_id: String,
194        is_local_device: bool,
195    },
196
197    /// An unknown event, most likely an event the client doesn't support yet.
198    ///
199    /// When receiving this event, the application should gracefully ignore it.
200    Unknown,
201}
202
203#[derive(uniffi::Enum, Debug)]
204/// A command invoked by another device.
205///
206/// This enum represents all possible commands that can be invoked on
207/// the device. It is the responsibility of the application to interpret
208/// each command.
209pub enum IncomingDeviceCommand {
210    /// Indicates that a tab has been sent to this device.
211    TabReceived {
212        sender: Option<Device>,
213        payload: SendTabPayload,
214    },
215    /// Indicates that the sender wants to close one or more tabs on this device.
216    TabsClosed {
217        sender: Option<Device>,
218        payload: CloseTabsPayload,
219    },
220}
221
222#[derive(uniffi::Record, Debug)]
223/// The payload sent when invoking a "send tab" command.
224pub struct SendTabPayload {
225    /// The navigation history of the sent tab.
226    ///
227    /// The last item in this list represents the page to be displayed,
228    /// while earlier items may be included in the navigation history
229    /// as a convenience to the user.
230    pub entries: Vec<TabHistoryEntry>,
231    /// A unique identifier to be included in send-tab metrics.
232    ///
233    /// The application should treat this as opaque.
234    #[uniffi(default = "")]
235    pub flow_id: String,
236    /// A unique identifier to be included in send-tab metrics.
237    ///
238    /// The application should treat this as opaque.
239    #[uniffi(default = "")]
240    pub stream_id: String,
241}
242
243#[derive(uniffi::Record, Debug)]
244/// The payload sent when invoking a "close tabs" command.
245pub struct CloseTabsPayload {
246    /// The URLs of the tabs to close.
247    pub urls: Vec<String>,
248}
249
250#[derive(uniffi::Record, Debug)]
251/// An individual entry in the navigation history of a sent tab.
252pub struct TabHistoryEntry {
253    pub title: String,
254    pub url: String,
255    #[uniffi(default = false)]
256    pub is_private: bool,
257}