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