diff --git a/components/sync15/ios/SyncUnlockInfo.swift b/components/sync15/ios/SyncUnlockInfo.swift index 26f86a0a92..aa80947ebf 100644 --- a/components/sync15/ios/SyncUnlockInfo.swift +++ b/components/sync15/ios/SyncUnlockInfo.swift @@ -12,12 +12,14 @@ open class SyncUnlockInfo { public var syncKey: String public var tokenserverURL: String public var loginEncryptionKey: String + public var tabsLocalId: String - public init(kid: String, fxaAccessToken: String, syncKey: String, tokenserverURL: String, loginEncryptionKey: String) { + public init(kid: String, fxaAccessToken: String, syncKey: String, tokenserverURL: String, loginEncryptionKey: String, tabsLocalId: String) { self.kid = kid self.fxaAccessToken = fxaAccessToken self.syncKey = syncKey self.tokenserverURL = tokenserverURL self.loginEncryptionKey = loginEncryptionKey + self.tabsLocalId = tabsLocalId } } diff --git a/components/tabs/android/src/main/java/mozilla/appservices/tabs/DeviceType.kt b/components/tabs/android/src/main/java/mozilla/appservices/tabs/DeviceType.kt new file mode 100644 index 0000000000..d102df50db --- /dev/null +++ b/components/tabs/android/src/main/java/mozilla/appservices/tabs/DeviceType.kt @@ -0,0 +1,10 @@ +/* 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/. */ + +package mozilla.appservices.remotetabs + +// We needed to rename the tabs `DeviceType` as it conflicts with the `DeviceType` we are exposing for FxA +// in iOS. However renaming `DeviceType` to `TabsDeviceType` creates a breaking change for the Android code. +// So we are aliasing `TabsDeviceType` back to `DeviceType` in order to prevent the breaking change. +typealias DeviceType = TabsDeviceType diff --git a/components/tabs/android/src/main/java/mozilla/appservices/tabs/RemoteTab.kt b/components/tabs/android/src/main/java/mozilla/appservices/tabs/RemoteTab.kt new file mode 100644 index 0000000000..bc641f8e56 --- /dev/null +++ b/components/tabs/android/src/main/java/mozilla/appservices/tabs/RemoteTab.kt @@ -0,0 +1,10 @@ +/* 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/. */ + +package mozilla.appservices.remotetabs + +// We needed to rename the Rust `RemoteTab` struct to `RemoteTabRecord` in order to circumvent the naming conflict in +// iOS with the native `RemoteTab` struct. But that creates a breaking change for the Android code. So we are aliasing +// `RemoteTabRecord` back to `RemoteTab` to prevent a breaking change. +typealias RemoteTab = RemoteTabRecord diff --git a/components/tabs/ios/Tabs/Tabs.swift b/components/tabs/ios/Tabs/Tabs.swift new file mode 100644 index 0000000000..1aa1c90f18 --- /dev/null +++ b/components/tabs/ios/Tabs/Tabs.swift @@ -0,0 +1,54 @@ +/* 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/. */ + +import Foundation + +open class TabsStorage { + private var store: TabsStore + private let queue = DispatchQueue(label: "com.mozilla.tabs-storage") + + public init(databasePath: String) { + store = TabsStore(path: databasePath) + } + + /// Get all tabs by client. + open func getAll() -> [ClientRemoteTabs] { + return queue.sync { + return self.store.getAll() + } + } + + /// Set the local tabs. + open func setLocalTabs(remoteTabs: [RemoteTabRecord]) { + queue.sync { + self.store.setLocalTabs(remoteTabs: remoteTabs) + } + } + + /// Register with the sync manager + open func registerWithSyncManager() { + return queue.sync { + return self.store.registerWithSyncManager() + } + } + + open func reset() throws { + try queue.sync { + try self.store.reset() + } + } + + open func sync(unlockInfo: SyncUnlockInfo) throws -> String { + return try queue.sync { + return try self.store + .sync( + keyId: unlockInfo.kid, + accessToken: unlockInfo.fxaAccessToken, + syncKey: unlockInfo.syncKey, + tokenserverUrl: unlockInfo.tokenserverURL, + localId: unlockInfo.tabsLocalId + ) + } + } +} diff --git a/components/tabs/src/error.rs b/components/tabs/src/error.rs index cefe38371f..c1f81c8ef8 100644 --- a/components/tabs/src/error.rs +++ b/components/tabs/src/error.rs @@ -3,10 +3,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #[derive(Debug, thiserror::Error)] -pub enum ErrorKind { +pub enum TabsError { #[error("Error synchronizing: {0}")] SyncAdapterError(#[from] sync15::Error), + #[error("Sync reset error: {0}")] + SyncResetError(#[from] anyhow::Error), + #[error("Error parsing JSON data: {0}")] JsonError(#[from] serde_json::Error), @@ -20,12 +23,4 @@ pub enum ErrorKind { OpenDatabaseError(#[from] sql_support::open_database::Error), } -error_support::define_error! { - ErrorKind { - (SyncAdapterError, sync15::Error), - (JsonError, serde_json::Error), - (UrlParseError, url::ParseError), - (SqlError, rusqlite::Error), - (OpenDatabaseError, sql_support::open_database::Error), - } -} +pub type Result = std::result::Result; diff --git a/components/tabs/src/lib.rs b/components/tabs/src/lib.rs index 052e07bac1..d822835d78 100644 --- a/components/tabs/src/lib.rs +++ b/components/tabs/src/lib.rs @@ -13,10 +13,10 @@ mod sync; uniffi_macros::include_scaffolding!("tabs"); -pub use crate::storage::{ClientRemoteTabs, RemoteTab}; +pub use crate::storage::{ClientRemoteTabs, RemoteTabRecord, TabsDeviceType}; pub use crate::sync::engine::TabsEngine; pub use crate::sync::store::TabsStore; -pub use error::{Error, ErrorKind, Result}; +use error::TabsError; use sync15::DeviceType; pub use crate::sync::store::get_registered_sync_engine; diff --git a/components/tabs/src/storage.rs b/components/tabs/src/storage.rs index 092e66d12f..8d25808190 100644 --- a/components/tabs/src/storage.rs +++ b/components/tabs/src/storage.rs @@ -16,6 +16,9 @@ use sql_support::ConnExt; use std::cell::RefCell; use std::path::{Path, PathBuf}; +pub type TabsDeviceType = crate::DeviceType; +pub type RemoteTabRecord = RemoteTab; + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct RemoteTab { pub title: String, diff --git a/components/tabs/src/sync/store.rs b/components/tabs/src/sync/store.rs index 11f532b4a2..46757272a0 100644 --- a/components/tabs/src/sync/store.rs +++ b/components/tabs/src/sync/store.rs @@ -9,10 +9,7 @@ use interrupt_support::NeverInterrupts; use std::cell::RefCell; use std::path::Path; use std::sync::{Arc, Mutex, Weak}; -use sync15::{ - sync_multiple, telemetry, KeyBundle, MemoryCachedState, Sync15StorageClientInit, SyncEngine, - SyncEngineId, -}; +use sync15::{sync_multiple, EngineSyncAssociation, MemoryCachedState, SyncEngine, SyncEngineId}; // Our "sync manager" will use whatever is stashed here. lazy_static::lazy_static! { @@ -69,21 +66,37 @@ impl TabsStore { self.storage.lock().unwrap().get_remote_tabs() } + pub fn reset(self: Arc) -> Result<()> { + let engine = TabsEngine::new(Arc::clone(&self)); + engine.reset(&EngineSyncAssociation::Disconnected)?; + Ok(()) + } + /// A convenience wrapper around sync_multiple. pub fn sync( self: Arc, - storage_init: &Sync15StorageClientInit, - root_sync_key: &KeyBundle, - local_id: &str, - ) -> Result { + key_id: String, + access_token: String, + sync_key: String, + tokenserver_url: String, + local_id: String, + ) -> Result { let mut mem_cached_state = MemoryCachedState::default(); let mut engine = TabsEngine::new(Arc::clone(&self)); + // Since we are syncing without the sync manager, there's no // command processor, therefore no clients engine, and in // consequence `TabsStore::prepare_for_sync` is never called // which means our `local_id` will never be set. // Do it here. - engine.local_id = RefCell::new(local_id.to_owned()); + engine.local_id = RefCell::new(local_id); + + let storage_init = &sync15::Sync15StorageClientInit { + key_id, + access_token, + tokenserver_url: url::Url::parse(tokenserver_url.as_str())?, + }; + let root_sync_key = &sync15::KeyBundle::from_ksync_base64(sync_key.as_str())?; let mut result = sync_multiple( &[&engine], @@ -103,7 +116,7 @@ impl TabsStore { return Err(e.into()); } match result.engine_results.remove("tabs") { - None | Some(Ok(())) => Ok(result.telemetry), + None | Some(Ok(())) => Ok(serde_json::to_string(&result.telemetry).unwrap()), Some(Err(e)) => Err(e.into()), } } diff --git a/components/tabs/src/tabs.udl b/components/tabs/src/tabs.udl index 19e672bb4c..8f0c3b4dc5 100644 --- a/components/tabs/src/tabs.udl +++ b/components/tabs/src/tabs.udl @@ -2,25 +2,41 @@ namespace tabs { }; +[Error] +enum TabsError { + "SyncAdapterError", + "SyncResetError", + "JsonError", + "UrlParseError", + "SqlError", + "OpenDatabaseError", +}; + interface TabsStore { constructor(string path); sequence get_all(); - void set_local_tabs(sequence remote_tabs); + void set_local_tabs(sequence remote_tabs); [Self=ByArc] void register_with_sync_manager(); + + [Throws=TabsError, Self=ByArc] + void reset(); + + [Throws=TabsError, Self=ByArc] + string sync(string key_id, string access_token, string sync_key, string tokenserver_url, string local_id); }; // Note that this enum is duplicated in fxa-client.udl (although the underlying type *is* // shared). This duplication exists because there's no direct dependency between that crate and // this one. We can probably remove the duplication when sync15 gets a .udl file, then we could // reference it via an `[Extern=...]typedef` -enum DeviceType { "Desktop", "Mobile", "Tablet", "VR", "TV", "Unknown" }; +enum TabsDeviceType { "Desktop", "Mobile", "Tablet", "VR", "TV", "Unknown" }; -dictionary RemoteTab { +dictionary RemoteTabRecord { string title; sequence url_history; string? icon; @@ -30,6 +46,6 @@ dictionary RemoteTab { dictionary ClientRemoteTabs { string client_id; string client_name; - DeviceType device_type; - sequence remote_tabs; + TabsDeviceType device_type; + sequence remote_tabs; }; diff --git a/examples/cli-support/src/fxa_creds.rs b/examples/cli-support/src/fxa_creds.rs index 039b4638e3..6119c1f075 100644 --- a/examples/cli-support/src/fxa_creds.rs +++ b/examples/cli-support/src/fxa_creds.rs @@ -78,7 +78,7 @@ pub fn get_default_fxa_config() -> Config { Config::release(CLIENT_ID, REDIRECT_URI) } -fn get_account_and_token( +pub fn get_account_and_token( config: Config, cred_file: &str, ) -> Result<(FirefoxAccount, AccessTokenInfo)> { diff --git a/examples/tabs-sync/src/tabs-sync.rs b/examples/tabs-sync/src/tabs-sync.rs index 92ecc2c086..4c7717947a 100644 --- a/examples/tabs-sync/src/tabs-sync.rs +++ b/examples/tabs-sync/src/tabs-sync.rs @@ -4,12 +4,12 @@ #![warn(rust_2018_idioms)] -use cli_support::fxa_creds::{get_cli_fxa, get_default_fxa_config}; +use cli_support::fxa_creds::{get_account_and_token, get_cli_fxa, get_default_fxa_config}; use cli_support::prompt::prompt_char; use std::path::Path; use std::sync::Arc; use structopt::StructOpt; -use tabs::{RemoteTab, TabsStore}; +use tabs::{RemoteTabRecord, TabsStore}; use anyhow::Result; @@ -42,6 +42,9 @@ fn main() -> Result<()> { cli_support::init_logging(); let opts = Opts::from_args(); + let (_, token_info) = get_account_and_token(get_default_fxa_config(), &opts.creds_file)?; + let sync_key = token_info.key.unwrap().kid; + let mut cli_fxa = get_cli_fxa(get_default_fxa_config(), &opts.creds_file)?; let device_id = cli_fxa.account.get_current_device_id()?; @@ -86,9 +89,11 @@ fn main() -> Result<()> { 'S' | 's' => { log::info!("Syncing!"); match Arc::clone(&store).sync( - &cli_fxa.client_init, - &cli_fxa.root_sync_key, - &device_id, + cli_fxa.client_init.clone().key_id, + cli_fxa.client_init.clone().access_token, + sync_key.clone(), + cli_fxa.client_init.tokenserver_url.to_string(), + device_id.clone(), ) { Err(e) => { log::warn!("Sync failed! {}", e); @@ -118,7 +123,7 @@ fn main() -> Result<()> { } #[cfg(feature = "with-clipboard")] -fn read_local_state() -> Vec { +fn read_local_state() -> Vec { use clipboard::{ClipboardContext, ClipboardProvider}; println!("Please run the following command in the Firefox Browser Toolbox and copy it."); println!( @@ -151,7 +156,7 @@ fn read_local_state() -> Vec { .iter() .map(|u| u.as_str().unwrap().to_owned()) .collect(); - local_state.push(RemoteTab { + local_state.push(RemoteTabRecord { title, url_history, icon, @@ -162,7 +167,7 @@ fn read_local_state() -> Vec { } #[cfg(not(feature = "with-clipboard"))] -fn read_local_state() -> Vec { +fn read_local_state() -> Vec { println!("This module is build without the `clipboard` feature, so we can't"); println!("read the local state."); vec![]