summaryrefslogtreecommitdiffstats
path: root/services/fxaccounts/rust-bridge
diff options
context:
space:
mode:
Diffstat (limited to 'services/fxaccounts/rust-bridge')
-rw-r--r--services/fxaccounts/rust-bridge/FirefoxAccountsBridge.h32
-rw-r--r--services/fxaccounts/rust-bridge/components.conf13
-rw-r--r--services/fxaccounts/rust-bridge/firefox-accounts-bridge/Cargo.toml24
-rw-r--r--services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/bridge.rs358
-rw-r--r--services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/lib.rs41
-rw-r--r--services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/error.rs109
-rw-r--r--services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/mod.rs14
-rw-r--r--services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/punt.rs124
-rw-r--r--services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/task.rs523
-rw-r--r--services/fxaccounts/rust-bridge/moz.build17
-rw-r--r--services/fxaccounts/rust-bridge/mozIFirefoxAccountsBridge.idl58
11 files changed, 1313 insertions, 0 deletions
diff --git a/services/fxaccounts/rust-bridge/FirefoxAccountsBridge.h b/services/fxaccounts/rust-bridge/FirefoxAccountsBridge.h
new file mode 100644
index 0000000000..79ccf01a02
--- /dev/null
+++ b/services/fxaccounts/rust-bridge/FirefoxAccountsBridge.h
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_FirefoxAccountsBridge_h_
+#define mozilla_FirefoxAccountsBridge_h_
+
+#include "mozIFirefoxAccountsBridge.h"
+#include "nsCOMPtr.h"
+
+extern "C" {
+
+// Implemented in Rust, in the `firefox-accounts-bridge` crate.
+nsresult NS_NewFirefoxAccountsBridge(mozIFirefoxAccountsBridge** aResult);
+
+} // extern "C"
+
+namespace mozilla {
+
+// A C++ XPCOM class constructor, for `components.conf`.
+already_AddRefed<mozIFirefoxAccountsBridge> NewFirefoxAccountsBridge() {
+ nsCOMPtr<mozIFirefoxAccountsBridge> bridge;
+ if (NS_WARN_IF(
+ NS_FAILED(NS_NewFirefoxAccountsBridge(getter_AddRefs(bridge))))) {
+ return nullptr;
+ }
+ return bridge.forget();
+}
+
+} // namespace mozilla
+
+#endif // mozilla_FirefoxAccountsBridge_h_
diff --git a/services/fxaccounts/rust-bridge/components.conf b/services/fxaccounts/rust-bridge/components.conf
new file mode 100644
index 0000000000..a0ebf8645d
--- /dev/null
+++ b/services/fxaccounts/rust-bridge/components.conf
@@ -0,0 +1,13 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Classes = [
+ {
+ 'cid': '{e2e4fbac-7443-11ea-ae79-f215fc2ca371}',
+ 'contract_ids': ['@mozilla.org/services/firefox-accounts-bridge;1'],
+ 'type': 'mozIFirefoxAccountsBridge',
+ 'headers': ['mozilla/FirefoxAccountsBridge.h'],
+ 'constructor': 'mozilla::NewFirefoxAccountsBridge',
+ }
+]
diff --git a/services/fxaccounts/rust-bridge/firefox-accounts-bridge/Cargo.toml b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/Cargo.toml
new file mode 100644
index 0000000000..571cd94047
--- /dev/null
+++ b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "firefox-accounts-bridge"
+version = "0.1.0"
+authors = ["The Synced Clients Integrations team <sync-team@mozilla.com>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+atomic_refcell = "0.1"
+cstr = "0.2"
+libc = "0.2"
+once_cell = "1"
+paste = "0.1"
+serde = "1"
+serde_json = "1"
+url = "2.1"
+moz_task = { path = "../../../../xpcom/rust/moz_task" }
+nserror = { path = "../../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../../xpcom/rust/nsstring" }
+xpcom = { path = "../../../../xpcom/rust/xpcom" }
+storage_variant = { path = "../../../../storage/variant" }
+thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
+fxa-client = { git = "https://github.com/mozilla/application-services", rev = "8a576fbe79199fa8664f64285524017f74ebcc5f", features = ["gecko"] }
diff --git a/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/bridge.rs b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/bridge.rs
new file mode 100644
index 0000000000..64f0e222bc
--- /dev/null
+++ b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/bridge.rs
@@ -0,0 +1,358 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::punt::{
+ error::{Error, Result},
+ PuntTask,
+};
+use fxa_client::FirefoxAccount;
+use nserror::{nsresult, NS_OK};
+use nsstring::{nsACString, nsCString};
+use once_cell::unsync::OnceCell;
+use std::{
+ str,
+ sync::{Arc, Mutex},
+};
+use storage_variant::HashPropertyBag;
+use thin_vec::ThinVec;
+use xpcom::{
+ interfaces::{mozIFirefoxAccountsBridgeCallback, nsIPropertyBag, nsISerialEventTarget},
+ RefPtr,
+};
+
+/// This macro calls `PuntTask::for_<fn_name>(fxa, <..args>, callback)`
+/// if `fxa` has been initialized.
+macro_rules! punt {
+ ($fn_name:ident $(, $arg:ident : $ty:ty)*) => {
+ fn $fn_name(&self $(, $arg: $ty)*, callback: &mozIFirefoxAccountsBridgeCallback) -> Result<()> {
+ if let Some(fxa) = self.fxa.get() {
+ let task_fn = paste::expr! { PuntTask::[<for_ $fn_name>] };
+ let task = task_fn(fxa $(, $arg)*, callback)?;
+ task.dispatch(&self.thread)?;
+ Ok(())
+ } else {
+ Err(Error::Unavailable)
+ }
+ }
+ }
+}
+
+/// An XPCOM binding for the Rust Firefox Accounts crate.
+#[derive(xpcom)]
+#[xpimplements(mozIFirefoxAccountsBridge)]
+#[refcnt = "nonatomic"] // Non-atomic because we have a `RefCell`.
+pub struct InitBridge {
+ // A background task queue used to run all our operations
+ // on a thread pool.
+ thread: RefPtr<nsISerialEventTarget>,
+ fxa: OnceCell<Arc<Mutex<FirefoxAccount>>>,
+}
+
+impl Bridge {
+ pub fn new() -> Result<RefPtr<Bridge>> {
+ let thread = moz_task::create_background_task_queue(cstr!("FirefoxAccountsBridge"))?;
+ Ok(Bridge::allocate(InitBridge {
+ thread,
+ fxa: OnceCell::new(),
+ }))
+ }
+
+ // This method (or InitFromJSON) must be called before any other one, because this
+ // is where we can finally give parameters for the Rust `FirefoxAccount` constructor
+ // and create an instance.
+ xpcom_method!(
+ init => Init(
+ options: *const nsIPropertyBag
+ )
+ );
+ fn init(&self, options: &nsIPropertyBag) -> Result<()> {
+ let options = HashPropertyBag::clone_from_bag(options)?;
+ let content_url: nsCString = options.get("content_url")?;
+ let content_url = str::from_utf8(&*content_url)?;
+ let client_id: nsCString = options.get("client_id")?;
+ let client_id = str::from_utf8(&*client_id)?;
+ let redirect_uri: nsCString = options.get("redirect_uri")?;
+ let redirect_uri = str::from_utf8(&*redirect_uri)?;
+ let token_server_url_override: nsCString = options.get("token_server_url_override")?;
+ let token_server_url_override = if token_server_url_override.is_empty() {
+ None
+ } else {
+ Some(str::from_utf8(&*token_server_url_override)?)
+ };
+ self.fxa
+ .set(Arc::new(Mutex::new(FirefoxAccount::new(
+ &content_url,
+ &client_id,
+ &redirect_uri,
+ token_server_url_override,
+ ))))
+ .map_err(|_| Error::AlreadyInitialized)?;
+ Ok(())
+ }
+
+ xpcom_method!(
+ from_json => InitFromJSON(
+ json: *const nsACString
+ )
+ );
+ fn from_json(&self, json: &nsACString) -> Result<()> {
+ let json = str::from_utf8(&*json)?;
+ self.fxa
+ .set(Arc::new(Mutex::new(FirefoxAccount::from_json(json)?)))
+ .map_err(|_| Error::AlreadyInitialized)?;
+ Ok(())
+ }
+
+ xpcom_method!(
+ to_json => StateJSON(
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+
+ punt!(to_json);
+
+ xpcom_method!(
+ begin_oauth_flow => BeginOAuthFlow(
+ scopes: *const ThinVec<nsCString>,
+ entry_point: *const nsACString,
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+
+ punt!(
+ begin_oauth_flow,
+ scopes: &ThinVec<nsCString>,
+ entry_point: &nsACString
+ );
+
+ xpcom_method!(
+ complete_oauth_flow => CompleteOAuthFlow(
+ code: *const nsACString,
+ state: *const nsACString,
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+
+ punt!(complete_oauth_flow, code: &nsACString, state: &nsACString);
+
+ xpcom_method!(
+ disconnect => Disconnect(
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+ punt!(disconnect);
+
+ xpcom_method!(
+ get_access_token => GetAccessToken(
+ scope: *const nsACString,
+ ttl: u64,
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+ punt!(get_access_token, scope: &nsACString, ttl: u64);
+
+ xpcom_method!(
+ get_session_token => GetSessionToken(
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+
+ punt!(get_session_token);
+
+ xpcom_method!(
+ get_attached_clients => GetAttachedClients(
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+ punt!(get_attached_clients);
+
+ xpcom_method!(
+ check_authorization_status => CheckAuthorizationStatus(
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+ punt!(check_authorization_status);
+
+ xpcom_method!(
+ clear_access_token_cache => ClearAccessTokenCache(
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+ punt!(clear_access_token_cache);
+
+ xpcom_method!(
+ handle_session_token_change => HandleSessionTokenChange(
+ session_token: *const nsACString,
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+
+ punt!(handle_session_token_change, session_token: &nsACString);
+
+ xpcom_method!(
+ migrate_from_session_token => MigrateFromSessionToken(
+ session_token: *const nsACString,
+ k_sync: *const nsACString,
+ k_xcs: *const nsACString,
+ copy_session_token: bool,
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+
+ punt!(
+ migrate_from_session_token,
+ session_token: &nsACString,
+ k_sync: &nsACString,
+ k_xcs: &nsACString,
+ copy_session_token: bool
+ );
+
+ xpcom_method!(
+ retry_migrate_from_session_token => RetryMigrateFromSessionToken(
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+
+ punt!(retry_migrate_from_session_token);
+
+ xpcom_method!(
+ is_in_migration_state => IsInMigrationState(
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+ punt!(is_in_migration_state);
+
+ xpcom_method!(
+ get_profile => GetProfile(
+ ignore_cache: bool,
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+ punt!(get_profile, ignore_cache: bool);
+
+ xpcom_method!(
+ get_token_server_endpoint_url => GetTokenServerEndpointURL(
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+
+ punt!(get_token_server_endpoint_url);
+
+ xpcom_method!(
+ get_connection_success_url => GetConnectionSuccessURL(
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+
+ punt!(get_connection_success_url);
+
+ xpcom_method!(
+ get_manage_account_url => GetManageAccountURL(
+ entrypoint: *const nsACString,
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+ punt!(get_manage_account_url, entrypoint: &nsACString);
+
+ xpcom_method!(
+ get_manage_devices_url => GetManageDevicesURL(
+ entrypoint: *const nsACString,
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+ punt!(get_manage_devices_url, entrypoint: &nsACString);
+
+ xpcom_method!(
+ fetch_devices => FetchDevices(
+ ignore_cache: bool,
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+
+ punt!(fetch_devices, ignore_cache: bool);
+
+ xpcom_method!(
+ set_device_display_name => SetDeviceDisplayName(
+ name: *const nsACString,
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+ punt!(set_device_display_name, name: &nsACString);
+
+ xpcom_method!(
+ handle_push_message => HandlePushMessage(
+ payload: *const nsACString,
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+
+ punt!(handle_push_message, payload: &nsACString);
+
+ xpcom_method!(
+ poll_device_commands => PollDeviceCommands(
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+
+ punt!(poll_device_commands);
+
+ xpcom_method!(
+ send_single_tab => SendSingleTab(
+ target_id: *const nsACString,
+ title: *const nsACString,
+ url: *const nsACString,
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+ punt!(
+ send_single_tab,
+ target_id: &nsACString,
+ title: &nsACString,
+ url: &nsACString
+ );
+
+ xpcom_method!(
+ set_device_push_subscription => SetDevicePushSubscription(
+ endpoint: *const nsACString,
+ public_key: *const nsACString,
+ auth_key: *const nsACString,
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+
+ punt!(
+ set_device_push_subscription,
+ endpoint: &nsACString,
+ public_key: &nsACString,
+ auth_key: &nsACString
+ );
+
+ xpcom_method!(
+ initialize_device => InitializeDevice(
+ name: *const nsACString,
+ device_type: *const nsACString,
+ supported_capabilities: *const ThinVec<nsCString>,
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+
+ punt!(
+ initialize_device,
+ name: &nsACString,
+ device_type: &nsACString,
+ supported_capabilities: &ThinVec<nsCString>
+ );
+
+ xpcom_method!(
+ ensure_capabilities => EnsureCapabilities(
+ supported_capabilities: *const ThinVec<nsCString>,
+ callback: *const mozIFirefoxAccountsBridgeCallback
+ )
+ );
+
+ punt!(
+ ensure_capabilities,
+ supported_capabilities: &ThinVec<nsCString>
+ );
+}
diff --git a/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/lib.rs b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/lib.rs
new file mode 100644
index 0000000000..9d85ecfe22
--- /dev/null
+++ b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/lib.rs
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! This crate, as its name suggests, serves as a bridge
+//! between the Javascript world (RustFxAccount.js)
+//! and the Rust world (fxa-client crate).
+//!
+//! The `bridge` module implements the `mozIFirefoxAccountsBridge`
+//! interface, which is callable from JS.
+//!
+//! The `punt` module helps running the synchronous Rust operations
+//! on a background thread pool managed by Gecko.
+
+#[macro_use]
+extern crate cstr;
+#[macro_use]
+extern crate xpcom;
+
+mod bridge;
+mod punt;
+
+use crate::bridge::Bridge;
+use nserror::{nsresult, NS_OK};
+use xpcom::{interfaces::mozIFirefoxAccountsBridge, RefPtr};
+
+// The constructor for our fxa implementation, exposed to C++. See
+// `FirefoxAccountsBridge.h` for the declaration and the wrapper we
+// register with the component manager.
+#[no_mangle]
+pub unsafe extern "C" fn NS_NewFirefoxAccountsBridge(
+ result: *mut *const mozIFirefoxAccountsBridge,
+) -> nsresult {
+ match Bridge::new() {
+ Ok(bridge) => {
+ RefPtr::new(bridge.coerce::<mozIFirefoxAccountsBridge>()).forget(&mut *result);
+ NS_OK
+ }
+ Err(err) => err.into(),
+ }
+}
diff --git a/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/error.rs b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/error.rs
new file mode 100644
index 0000000000..0328e15aae
--- /dev/null
+++ b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/error.rs
@@ -0,0 +1,109 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::{error, fmt, result, str::Utf8Error, sync::PoisonError};
+
+use nserror::{
+ nsresult, NS_ERROR_ALREADY_INITIALIZED, NS_ERROR_FAILURE, NS_ERROR_INVALID_ARG,
+ NS_ERROR_NOT_AVAILABLE, NS_ERROR_UNEXPECTED,
+};
+
+pub type Result<T> = result::Result<T, Error>;
+
+#[derive(Debug)]
+pub enum Error {
+ /// A wrapped FxA error.
+ FxAError(fxa_client::Error),
+
+ /// A wrapped XPCOM error.
+ Nsresult(nsresult),
+
+ /// A punt already ran.
+ AlreadyRan(&'static str),
+
+ /// A punt didn't run on the background task queue.
+ DidNotRun(&'static str),
+
+ /// A punt was already torn down when used.
+ AlreadyTornDown,
+
+ /// A Gecko string couldn't be converted to UTF-8.
+ MalformedString(Box<dyn error::Error + Send + Sync + 'static>),
+
+ /// The FxA object has already been initialized.
+ AlreadyInitialized,
+
+ /// The FxA object has not been initialized before use.
+ Unavailable,
+
+ /// Failure acquiring a lock on the FxA object in the background thread.
+ PoisonError,
+}
+
+impl error::Error for Error {
+ fn source(&self) -> Option<&(dyn error::Error + 'static)> {
+ match self {
+ Error::MalformedString(error) => Some(error.as_ref()),
+ _ => None,
+ }
+ }
+}
+
+impl From<Error> for nsresult {
+ fn from(error: Error) -> nsresult {
+ match error {
+ Error::FxAError(_) => NS_ERROR_FAILURE,
+ Error::AlreadyRan(_)
+ | Error::DidNotRun(_)
+ | Error::AlreadyTornDown
+ | Error::PoisonError => NS_ERROR_UNEXPECTED,
+ Error::Nsresult(result) => result,
+ Error::MalformedString(_) => NS_ERROR_INVALID_ARG,
+ Error::AlreadyInitialized => NS_ERROR_ALREADY_INITIALIZED,
+ Error::Unavailable => NS_ERROR_NOT_AVAILABLE,
+ }
+ }
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Error::FxAError(error) => error.fmt(f),
+ Error::Nsresult(result) => write!(f, "Operation failed with {}", result.error_name()),
+ Error::AlreadyRan(what) => write!(f, "`{}` already ran on the background thread", what),
+ Error::DidNotRun(what) => write!(f, "Failed to run `{}` on background thread", what),
+ Error::AlreadyTornDown => {
+ write!(f, "Can't use a storage area that's already torn down")
+ }
+ Error::MalformedString(error) => error.fmt(f),
+ Error::AlreadyInitialized => f.write_str("The resource has already been initialized"),
+ Error::Unavailable => f.write_str("A resource for this operation is unavailable"),
+ Error::PoisonError => f.write_str("Error getting read/write lock"),
+ }
+ }
+}
+
+impl From<nsresult> for Error {
+ fn from(result: nsresult) -> Error {
+ Error::Nsresult(result)
+ }
+}
+
+impl From<Utf8Error> for Error {
+ fn from(error: Utf8Error) -> Error {
+ Error::MalformedString(error.into())
+ }
+}
+
+impl<T> From<PoisonError<T>> for Error {
+ fn from(_error: PoisonError<T>) -> Error {
+ Error::PoisonError
+ }
+}
+
+impl From<fxa_client::error::Error> for Error {
+ fn from(result: fxa_client::error::Error) -> Error {
+ Error::FxAError(result)
+ }
+}
diff --git a/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/mod.rs b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/mod.rs
new file mode 100644
index 0000000000..3a6cd63cbe
--- /dev/null
+++ b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/mod.rs
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Punt is pretty much a copy-paste of the Golden Gate crate in services/sync/golden_gate
+//! but specialized to work on the fxa-client crate.
+//! In short it helps run Rust code in a background thread, handle errors and get the results back.
+//! There's an effort to factorize these helpers in https://bugzilla.mozilla.org/show_bug.cgi?id=1626703.
+
+pub mod error;
+mod punt;
+mod task;
+
+pub use task::PuntTask;
diff --git a/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/punt.rs b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/punt.rs
new file mode 100644
index 0000000000..79c16ee49f
--- /dev/null
+++ b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/punt.rs
@@ -0,0 +1,124 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use fxa_client::device::{
+ Capability as FxaDeviceCapability, PushSubscription as FxAPushSubscription,
+ Type as FxaDeviceType,
+};
+use nsstring::nsCString;
+use storage_variant::VariantType;
+use xpcom::{interfaces::nsIVariant, RefPtr};
+
+/// An operation that runs on the background thread, and optionally passes a
+/// result to its callback.
+pub enum Punt {
+ ToJson,
+ BeginOAuthFlow(Vec<String>, String),
+ CompleteOAuthFlow(String, String),
+ Disconnect,
+ GetAccessToken(String, Option<u64>),
+ GetSessionToken,
+ GetAttachedClients,
+ CheckAuthorizationStatus,
+ ClearAccessTokenCache,
+ HandleSessionTokenChange(String),
+ MigrateFromSessionToken(String, String, String, bool),
+ RetryMigrateFromSessionToken,
+ IsInMigrationState,
+ GetProfile(bool),
+ GetTokenServerEndpointUrl,
+ GetConnectionSuccessUrl,
+ GetManageAccountUrl(String),
+ GetManageDevicesUrl(String),
+ FetchDevices(bool),
+ SetDeviceDisplayName(String),
+ HandlePushMessage(String),
+ PollDeviceCommands,
+ SendSingleTab(String, String, String),
+ SetDevicePushSubscription(FxAPushSubscription),
+ InitializeDevice(String, FxaDeviceType, Vec<FxaDeviceCapability>),
+ EnsureCapabilities(Vec<FxaDeviceCapability>),
+}
+
+impl Punt {
+ /// Returns the operation name for debugging and labeling the task
+ /// runnable.
+ pub fn name(&self) -> &'static str {
+ match self {
+ Punt::ToJson => concat!(module_path!(), "toJson"),
+ Punt::BeginOAuthFlow { .. } => concat!(module_path!(), "beginOAuthFlow"),
+ Punt::CompleteOAuthFlow { .. } => concat!(module_path!(), "completeOAuthFlow"),
+ Punt::Disconnect => concat!(module_path!(), "disconnect"),
+ Punt::GetAccessToken { .. } => concat!(module_path!(), "getAccessToken"),
+ Punt::GetSessionToken => concat!(module_path!(), "getSessionToken"),
+ Punt::GetAttachedClients => concat!(module_path!(), "getAttachedClients"),
+ Punt::CheckAuthorizationStatus => concat!(module_path!(), "checkAuthorizationStatus"),
+ Punt::ClearAccessTokenCache => concat!(module_path!(), "clearAccessTokenCache"),
+ Punt::HandleSessionTokenChange { .. } => {
+ concat!(module_path!(), "handleSessionTokenChange")
+ }
+ Punt::MigrateFromSessionToken { .. } => {
+ concat!(module_path!(), "migrateFromSessionToken")
+ }
+ Punt::RetryMigrateFromSessionToken => {
+ concat!(module_path!(), "retryMigrateFromSessionToken")
+ }
+ Punt::IsInMigrationState => concat!(module_path!(), "isInMigrationState"),
+ Punt::GetProfile { .. } => concat!(module_path!(), "getProfile"),
+ Punt::GetTokenServerEndpointUrl => concat!(module_path!(), "getTokenServerEndpointUrl"),
+ Punt::GetConnectionSuccessUrl => concat!(module_path!(), "getConnectionSuccessUrl"),
+ Punt::GetManageAccountUrl { .. } => concat!(module_path!(), "getManageAccountUrl"),
+ Punt::GetManageDevicesUrl { .. } => concat!(module_path!(), "getManageDevicesUrl"),
+ Punt::FetchDevices { .. } => concat!(module_path!(), "fetchDevices"),
+ Punt::SetDeviceDisplayName { .. } => concat!(module_path!(), "setDeviceDisplayName"),
+ Punt::HandlePushMessage { .. } => concat!(module_path!(), "handlePushMessage"),
+ Punt::PollDeviceCommands => concat!(module_path!(), "pollDeviceCommands"),
+ Punt::SendSingleTab { .. } => concat!(module_path!(), "sendSingleTab"),
+ Punt::SetDevicePushSubscription { .. } => {
+ concat!(module_path!(), "setDevicePushSubscription")
+ }
+ Punt::InitializeDevice { .. } => concat!(module_path!(), "initializeDevice"),
+ Punt::EnsureCapabilities { .. } => concat!(module_path!(), "ensureCapabilities"),
+ }
+ }
+}
+
+/// The result of a punt task, sent from the background thread back to the
+/// main thread. Results are converted to variants, and passed as arguments to
+/// the callbacks.
+pub enum PuntResult {
+ String(String),
+ Boolean(bool),
+ Null,
+}
+
+impl PuntResult {
+ pub fn url_spec(url: url::Url) -> Self {
+ PuntResult::String(url.into_string())
+ }
+ pub fn json_stringify<T>(value: T) -> Self
+ where
+ T: serde::Serialize + std::marker::Sized,
+ {
+ PuntResult::String(serde_json::to_string(&value).unwrap())
+ }
+}
+
+impl From<()> for PuntResult {
+ fn from(_: ()) -> PuntResult {
+ PuntResult::Null
+ }
+}
+
+impl PuntResult {
+ /// Converts the result to an `nsIVariant` that can be passed as an
+ /// argument to `callback.handleResult()`.
+ pub fn into_variant(self) -> RefPtr<nsIVariant> {
+ match self {
+ PuntResult::String(v) => nsCString::from(v).into_variant(),
+ PuntResult::Boolean(b) => b.into_variant(),
+ PuntResult::Null => ().into_variant(),
+ }
+ }
+}
diff --git a/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/task.rs b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/task.rs
new file mode 100644
index 0000000000..2e2c8ad68e
--- /dev/null
+++ b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/task.rs
@@ -0,0 +1,523 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::punt::{
+ error::{self, Error},
+ punt::{Punt, PuntResult},
+};
+use atomic_refcell::AtomicRefCell;
+use fxa_client::{
+ device::{
+ Capability as FxaDeviceCapability, CommandFetchReason,
+ PushSubscription as FxaPushSubscription, Type as FxaDeviceType,
+ },
+ FirefoxAccount,
+};
+use moz_task::{DispatchOptions, Task, TaskRunnable, ThreadPtrHandle, ThreadPtrHolder};
+use nserror::nsresult;
+use nsstring::{nsACString, nsCString};
+use std::{
+ fmt::Write,
+ mem, str,
+ sync::{Arc, Mutex, Weak},
+};
+use xpcom::{
+ interfaces::{mozIFirefoxAccountsBridgeCallback, nsIEventTarget},
+ RefPtr,
+};
+
+/// A punt task sends an operation to an fxa instance on a
+/// background thread or task queue, and ferries back an optional result to
+/// a callback.
+pub struct PuntTask {
+ name: &'static str,
+ fxa: Weak<Mutex<FirefoxAccount>>,
+ punt: AtomicRefCell<Option<Punt>>,
+ callback: ThreadPtrHandle<mozIFirefoxAccountsBridgeCallback>,
+ result: AtomicRefCell<Result<PuntResult, Error>>,
+}
+
+impl PuntTask {
+ /// Creates a task for a punt. The `callback` is bound to the current
+ /// thread, and will be called once, after the punt returns from the
+ /// background thread.
+ fn new(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ punt: Punt,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let name = punt.name();
+ Ok(Self {
+ name: punt.name(),
+ fxa: Arc::downgrade(fxa),
+ punt: AtomicRefCell::new(Some(punt)),
+ callback: ThreadPtrHolder::new(
+ cstr!("mozIFirefoxAccountsBridgeCallback"),
+ RefPtr::new(callback),
+ )?,
+ result: AtomicRefCell::new(Err(Error::DidNotRun(name).into())),
+ })
+ }
+
+ /// Creates a task that calls begin_oauth_flow.
+ #[inline]
+ pub fn for_begin_oauth_flow(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ scopes: &[nsCString],
+ entry_point: &nsACString,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let scopes = scopes.iter().try_fold(
+ Vec::with_capacity(scopes.len()),
+ |mut acc, scope| -> error::Result<_> {
+ acc.push(std::str::from_utf8(&*scope)?.into());
+ Ok(acc)
+ },
+ )?;
+ let entry_point = str::from_utf8(&*entry_point)?.into();
+ Self::new(fxa, Punt::BeginOAuthFlow(scopes, entry_point), callback)
+ }
+
+ /// Creates a task that calls complete_oauth_flow.
+ #[inline]
+ pub fn for_complete_oauth_flow(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ code: &nsACString,
+ state: &nsACString,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let code = str::from_utf8(&*code)?.into();
+ let state = str::from_utf8(&*state)?.into();
+ Self::new(fxa, Punt::CompleteOAuthFlow(code, state), callback)
+ }
+
+ /// Creates a task that calls disconnect.
+ #[inline]
+ pub fn for_disconnect(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::Disconnect, callback)
+ }
+
+ /// Creates a task that calls to_json.
+ #[inline]
+ pub fn for_to_json(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::ToJson, callback)
+ }
+
+ /// Creates a task that calls get_access_token.
+ #[inline]
+ pub fn for_get_access_token(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ scope: &nsACString,
+ ttl: u64,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let scope = str::from_utf8(&*scope)?.into();
+ let ttl = if ttl > 0 { Some(ttl) } else { None };
+ Self::new(fxa, Punt::GetAccessToken(scope, ttl), callback)
+ }
+
+ /// Creates a task that calls get_session_token.
+ #[inline]
+ pub fn for_get_session_token(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::GetSessionToken, callback)
+ }
+
+ /// Creates a task that calls get_attached_clients.
+ #[inline]
+ pub fn for_get_attached_clients(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::GetAttachedClients, callback)
+ }
+
+ /// Creates a task that calls check_authorization_status.
+ #[inline]
+ pub fn for_check_authorization_status(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::CheckAuthorizationStatus, callback)
+ }
+
+ /// Creates a task that calls clear_access_token_cache.
+ #[inline]
+ pub fn for_clear_access_token_cache(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::ClearAccessTokenCache, callback)
+ }
+
+ /// Creates a task that calls handle_session_token_change.
+ #[inline]
+ pub fn for_handle_session_token_change(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ session_token: &nsACString,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let session_token = str::from_utf8(&*session_token)?.into();
+ Self::new(fxa, Punt::HandleSessionTokenChange(session_token), callback)
+ }
+
+ /// Creates a task that calls migrate_from_session_token.
+ #[inline]
+ pub fn for_migrate_from_session_token(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ session_token: &nsACString,
+ k_sync: &nsACString,
+ k_xcs: &nsACString,
+ copy_session_token: bool,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let session_token = str::from_utf8(&*session_token)?.into();
+ let k_sync = str::from_utf8(&*k_sync)?.into();
+ let k_xcs = str::from_utf8(&*k_xcs)?.into();
+ Self::new(
+ fxa,
+ Punt::MigrateFromSessionToken(session_token, k_sync, k_xcs, copy_session_token),
+ callback,
+ )
+ }
+
+ /// Creates a task that calls retry_migrate_from_session_token.
+ #[inline]
+ pub fn for_retry_migrate_from_session_token(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::RetryMigrateFromSessionToken, callback)
+ }
+
+ /// Creates a task that calls is_in_migration_state.
+ #[inline]
+ pub fn for_is_in_migration_state(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::IsInMigrationState, callback)
+ }
+
+ /// Creates a task that calls get_profile.
+ #[inline]
+ pub fn for_get_profile(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ ignore_cache: bool,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::GetProfile(ignore_cache), callback)
+ }
+
+ /// Creates a task that calls get_token_server_endpoint_url.
+ #[inline]
+ pub fn for_get_token_server_endpoint_url(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::GetTokenServerEndpointUrl, callback)
+ }
+
+ /// Creates a task that calls get_connection_success_url.
+ #[inline]
+ pub fn for_get_connection_success_url(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::GetConnectionSuccessUrl, callback)
+ }
+
+ /// Creates a task that calls get_manage_account_url.
+ #[inline]
+ pub fn for_get_manage_account_url(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ entrypoint: &nsACString,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let entrypoint = str::from_utf8(&*entrypoint)?.into();
+ Self::new(fxa, Punt::GetManageAccountUrl(entrypoint), callback)
+ }
+
+ /// Creates a task that calls get_manage_devices_url.
+ #[inline]
+ pub fn for_get_manage_devices_url(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ entrypoint: &nsACString,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let entrypoint = str::from_utf8(&*entrypoint)?.into();
+ Self::new(fxa, Punt::GetManageDevicesUrl(entrypoint), callback)
+ }
+
+ /// Creates a task that calls fetch_devices.
+ #[inline]
+ pub fn for_fetch_devices(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ ignore_cache: bool,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::FetchDevices(ignore_cache), callback)
+ }
+
+ /// Creates a task that calls set_device_display_name.
+ #[inline]
+ pub fn for_set_device_display_name(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ name: &nsACString,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let name = str::from_utf8(&*name)?.into();
+ Self::new(fxa, Punt::SetDeviceDisplayName(name), callback)
+ }
+
+ /// Creates a task that calls handle_push_message.
+ #[inline]
+ pub fn for_handle_push_message(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ payload: &nsACString,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let payload = str::from_utf8(&*payload)?.into();
+ Self::new(fxa, Punt::HandlePushMessage(payload), callback)
+ }
+
+ /// Creates a task that calls poll_device_commands.
+ #[inline]
+ pub fn for_poll_device_commands(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::PollDeviceCommands, callback)
+ }
+
+ /// Creates a task that calls send_single_tab.
+ #[inline]
+ pub fn for_send_single_tab(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ target_id: &nsACString,
+ title: &nsACString,
+ url: &nsACString,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let target_id = str::from_utf8(&*target_id)?.into();
+ let title = str::from_utf8(&*title)?.into();
+ let url = str::from_utf8(&*url)?.into();
+ Self::new(fxa, Punt::SendSingleTab(target_id, title, url), callback)
+ }
+
+ /// Creates a task that calls set_device_push_subscription.
+ #[inline]
+ pub fn for_set_device_push_subscription(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ endpoint: &nsACString,
+ public_key: &nsACString,
+ auth_key: &nsACString,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let push_subscription = FxaPushSubscription {
+ endpoint: str::from_utf8(&*endpoint)?.into(),
+ public_key: str::from_utf8(&*public_key)?.into(),
+ auth_key: str::from_utf8(&*auth_key)?.into(),
+ };
+ Self::new(
+ fxa,
+ Punt::SetDevicePushSubscription(push_subscription),
+ callback,
+ )
+ }
+
+ /// Creates a task that calls initialize_device.
+ #[inline]
+ pub fn for_initialize_device(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ name: &nsACString,
+ device_type: &nsACString,
+ supported_capabilities: &[nsCString],
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let name = str::from_utf8(&*name)?.into();
+ let device_type = to_device_type(device_type)?;
+ let supported_capabilities = to_capabilities(supported_capabilities)?;
+ Self::new(
+ fxa,
+ Punt::InitializeDevice(name, device_type, supported_capabilities),
+ callback,
+ )
+ }
+
+ /// Creates a task that calls ensure_capabilities.
+ #[inline]
+ pub fn for_ensure_capabilities(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ supported_capabilities: &[nsCString],
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let supported_capabilities = to_capabilities(supported_capabilities)?;
+ Self::new(
+ fxa,
+ Punt::EnsureCapabilities(supported_capabilities),
+ callback,
+ )
+ }
+
+ /// Dispatches the task to the given thread `target`.
+ pub fn dispatch(self, target: &nsIEventTarget) -> Result<(), Error> {
+ let runnable = TaskRunnable::new(self.name, Box::new(self))?;
+ // `may_block` schedules the task on the I/O thread pool, since we
+ // expect most operations to wait on I/O.
+ TaskRunnable::dispatch_with_options(
+ runnable,
+ target,
+ DispatchOptions::default().may_block(true),
+ )?;
+ Ok(())
+ }
+
+ fn run_with_punt(&self, punt: Punt) -> Result<PuntResult, Error> {
+ let fxa = self.fxa.upgrade().ok_or_else(|| Error::AlreadyTornDown)?;
+ let mut fxa = fxa.lock()?;
+ Ok(match punt {
+ Punt::ToJson => fxa.to_json().map(PuntResult::String),
+ Punt::BeginOAuthFlow(scopes, entry_point) => {
+ let scopes: Vec<&str> = scopes.iter().map(AsRef::as_ref).collect();
+ fxa.begin_oauth_flow(&scopes, &entry_point, None)
+ .map(PuntResult::String)
+ }
+ Punt::CompleteOAuthFlow(code, state) => fxa
+ .complete_oauth_flow(&code, &state)
+ .map(|_| PuntResult::Null),
+ Punt::Disconnect => {
+ fxa.disconnect();
+ Ok(PuntResult::Null)
+ }
+ Punt::GetAccessToken(scope, ttl) => fxa
+ .get_access_token(&scope, ttl)
+ .map(PuntResult::json_stringify),
+ Punt::GetSessionToken => fxa.get_session_token().map(PuntResult::String),
+ Punt::GetAttachedClients => fxa.get_attached_clients().map(PuntResult::json_stringify),
+ Punt::CheckAuthorizationStatus => fxa
+ .check_authorization_status()
+ .map(PuntResult::json_stringify),
+ Punt::ClearAccessTokenCache => {
+ fxa.clear_access_token_cache();
+ Ok(PuntResult::Null)
+ }
+ Punt::HandleSessionTokenChange(session_token) => fxa
+ .handle_session_token_change(&session_token)
+ .map(|_| PuntResult::Null),
+ Punt::MigrateFromSessionToken(session_token, k_sync, k_xcs, copy_session_token) => fxa
+ .migrate_from_session_token(&session_token, &k_sync, &k_xcs, copy_session_token)
+ .map(PuntResult::json_stringify),
+ Punt::RetryMigrateFromSessionToken => {
+ fxa.try_migration().map(PuntResult::json_stringify)
+ }
+ Punt::IsInMigrationState => {
+ Ok(PuntResult::Boolean(match fxa.is_in_migration_state() {
+ fxa_client::migrator::MigrationState::None => false,
+ _ => true,
+ }))
+ }
+ Punt::GetProfile(ignore_cache) => fxa
+ .get_profile(ignore_cache)
+ .map(PuntResult::json_stringify),
+ Punt::GetTokenServerEndpointUrl => fxa
+ .get_token_server_endpoint_url()
+ .map(PuntResult::url_spec),
+ Punt::GetConnectionSuccessUrl => {
+ fxa.get_connection_success_url().map(PuntResult::url_spec)
+ }
+ Punt::GetManageAccountUrl(entrypoint) => fxa
+ .get_manage_account_url(&entrypoint)
+ .map(PuntResult::url_spec),
+ Punt::GetManageDevicesUrl(entrypoint) => fxa
+ .get_manage_devices_url(&entrypoint)
+ .map(PuntResult::url_spec),
+ Punt::FetchDevices(ignore_cache) => fxa
+ .get_devices(ignore_cache)
+ .map(PuntResult::json_stringify),
+ Punt::SetDeviceDisplayName(name) => {
+ fxa.set_device_name(&name).map(PuntResult::json_stringify)
+ }
+ Punt::HandlePushMessage(payload) => fxa
+ .handle_push_message(&payload)
+ .map(PuntResult::json_stringify),
+ Punt::PollDeviceCommands => fxa
+ .poll_device_commands(CommandFetchReason::Poll)
+ .map(PuntResult::json_stringify),
+ Punt::SendSingleTab(target_id, title, url) => fxa
+ .send_tab(&target_id, &title, &url)
+ .map(|_| PuntResult::Null),
+ Punt::SetDevicePushSubscription(push_subscription) => fxa
+ .set_push_subscription(&push_subscription)
+ .map(PuntResult::json_stringify),
+ Punt::InitializeDevice(name, device_type, capabilities) => fxa
+ .initialize_device(&name, device_type.clone(), &capabilities)
+ .map(|_| PuntResult::Null),
+ Punt::EnsureCapabilities(capabilities) => fxa
+ .ensure_capabilities(&capabilities)
+ .map(|_| PuntResult::Null),
+ }?)
+ }
+}
+
+fn to_capabilities(capabilities: &[nsCString]) -> error::Result<Vec<FxaDeviceCapability>> {
+ Ok(capabilities.iter().try_fold(
+ Vec::with_capacity(capabilities.len()),
+ |mut acc, capability| -> error::Result<_> {
+ let capability = str::from_utf8(&*capability)?;
+ acc.push(match capability {
+ "sendTab" => FxaDeviceCapability::SendTab,
+ _ => return Err(Error::Nsresult(nserror::NS_ERROR_INVALID_ARG)),
+ });
+ Ok(acc)
+ },
+ )?)
+}
+
+fn to_device_type(device_type: &nsACString) -> error::Result<FxaDeviceType> {
+ let device_type = str::from_utf8(&*device_type)?;
+ Ok(match device_type {
+ "desktop" => FxaDeviceType::Desktop,
+ "mobile" => FxaDeviceType::Mobile,
+ "tablet" => FxaDeviceType::Tablet,
+ "tv" => FxaDeviceType::TV,
+ "vr" => FxaDeviceType::VR,
+ _ => return Err(Error::Nsresult(nserror::NS_ERROR_INVALID_ARG)),
+ })
+}
+
+impl Task for PuntTask {
+ fn run(&self) {
+ *self.result.borrow_mut() = match mem::take(&mut *self.punt.borrow_mut()) {
+ Some(punt) => self.run_with_punt(punt),
+ // A task should never run on the background queue twice, but we
+ // return an error just in case.
+ None => Err(Error::AlreadyRan(self.name)),
+ };
+ }
+
+ fn done(&self) -> Result<(), nsresult> {
+ let callback = self.callback.get().unwrap();
+ match mem::replace(
+ &mut *self.result.borrow_mut(),
+ Err(Error::AlreadyRan(self.name)),
+ ) {
+ Ok(result) => unsafe { callback.HandleSuccess(result.into_variant().coerce()) },
+ Err(err) => {
+ let mut message = nsCString::new();
+ write!(message, "{}", err).unwrap();
+ unsafe { callback.HandleError(err.into(), &*message) }
+ }
+ }
+ .to_result()
+ }
+}
diff --git a/services/fxaccounts/rust-bridge/moz.build b/services/fxaccounts/rust-bridge/moz.build
new file mode 100644
index 0000000000..e383036013
--- /dev/null
+++ b/services/fxaccounts/rust-bridge/moz.build
@@ -0,0 +1,17 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_MODULE = "firefox_accounts_bridge"
+
+XPIDL_SOURCES += [
+ "mozIFirefoxAccountsBridge.idl",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+EXPORTS.mozilla += [
+ "FirefoxAccountsBridge.h",
+]
diff --git a/services/fxaccounts/rust-bridge/mozIFirefoxAccountsBridge.idl b/services/fxaccounts/rust-bridge/mozIFirefoxAccountsBridge.idl
new file mode 100644
index 0000000000..f896242448
--- /dev/null
+++ b/services/fxaccounts/rust-bridge/mozIFirefoxAccountsBridge.idl
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPropertyBag;
+interface nsIVariant;
+
+// A generic callback called with a result. Variants are automatically unboxed
+// in JavaScript: for example, a `UTF8String` will be passed as a string
+// argument; an `Int32` or `Int64` as a number. Methods that don't return a
+// value will pass a `null` variant to `handleSuccess`.
+// For all callback types in this file, either `handleSuccess`
+// or `handleError` is guaranteed to be called once.
+[uuid(341b13a2-d121-4799-9870-9900861d98a0), scriptable]
+interface mozIFirefoxAccountsBridgeCallback : nsISupports {
+ void handleSuccess(in nsIVariant result);
+ void handleError(in nsresult code, in AUTF8String message);
+};
+
+[uuid(f33d083c-7443-11ea-bc8c-a517fc2ca371), scriptable]
+interface mozIFirefoxAccountsBridge : nsISupports {
+ void init(in nsIPropertyBag options);
+ void initFromJSON(in AUTF8String json);
+ void stateJSON(in mozIFirefoxAccountsBridgeCallback callback);
+
+ void beginOAuthFlow(in Array<AUTF8String> scopes, in AUTF8String entryPoint, in mozIFirefoxAccountsBridgeCallback callback);
+ void completeOAuthFlow(in AUTF8String code, in AUTF8String state, in mozIFirefoxAccountsBridgeCallback callback);
+ void disconnect(in mozIFirefoxAccountsBridgeCallback callback);
+
+ void getAccessToken(in AUTF8String scope, in unsigned long long ttl, in mozIFirefoxAccountsBridgeCallback callback);
+ void getSessionToken(in mozIFirefoxAccountsBridgeCallback callback);
+ void getAttachedClients(in mozIFirefoxAccountsBridgeCallback callback);
+ void checkAuthorizationStatus(in mozIFirefoxAccountsBridgeCallback callback);
+ void clearAccessTokenCache(in mozIFirefoxAccountsBridgeCallback callback);
+ void handleSessionTokenChange(in AUTF8String sessionToken, in mozIFirefoxAccountsBridgeCallback callback);
+
+ void migrateFromSessionToken(in AUTF8String sessionToken, in AUTF8String kSync, in AUTF8String kXCS, in boolean copySessionToken, in mozIFirefoxAccountsBridgeCallback callback);
+ void retryMigrateFromSessionToken(in mozIFirefoxAccountsBridgeCallback callback);
+ void isInMigrationState(in mozIFirefoxAccountsBridgeCallback callback);
+
+ void getProfile(in boolean ignoreCache, in mozIFirefoxAccountsBridgeCallback callback);
+
+ void getTokenServerEndpointURL(in mozIFirefoxAccountsBridgeCallback callback);
+ void getConnectionSuccessURL(in mozIFirefoxAccountsBridgeCallback callback);
+ void getManageAccountURL(in AUTF8String entrypoint, in mozIFirefoxAccountsBridgeCallback callback);
+ void getManageDevicesURL(in AUTF8String entrypoint, in mozIFirefoxAccountsBridgeCallback callback);
+
+ void fetchDevices(in boolean ignoreCache, in mozIFirefoxAccountsBridgeCallback callback);
+ void setDeviceDisplayName(in AUTF8String name, in mozIFirefoxAccountsBridgeCallback callback);
+ void handlePushMessage(in AUTF8String payload, in mozIFirefoxAccountsBridgeCallback callback);
+ void pollDeviceCommands(in mozIFirefoxAccountsBridgeCallback callback);
+ void sendSingleTab(in AUTF8String targetId, in AUTF8String title, in AUTF8String url, in mozIFirefoxAccountsBridgeCallback callback);
+ void setDevicePushSubscription(in AUTF8String endpoint, in AUTF8String publicKey, in AUTF8String authKey, in mozIFirefoxAccountsBridgeCallback callback);
+ void initializeDevice(in AUTF8String name, in AUTF8String deviceType, in Array<AUTF8String> supportedCapabilities, in mozIFirefoxAccountsBridgeCallback callback);
+ void ensureCapabilities(in Array<AUTF8String> supportedCapabilities, in mozIFirefoxAccountsBridgeCallback callback);
+};