Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Commit

Permalink
Part 3: Rust SyncManager integration
Browse files Browse the repository at this point in the history
Co-authored-by: Arturo Mejia <arturomejiamarmol@gmail.com>
  • Loading branch information
Grisha Kruglov and Amejia481 committed Sep 26, 2019
1 parent 6f89e8d commit 4ac03ab
Show file tree
Hide file tree
Showing 28 changed files with 820 additions and 190 deletions.
1 change: 1 addition & 0 deletions buildSrc/src/main/java/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ object Dependencies {

const val mozilla_sync_logins = "org.mozilla.appservices:logins:${Versions.mozilla_appservices}"
const val mozilla_places = "org.mozilla.appservices:places:${Versions.mozilla_appservices}"
const val mozilla_sync_manager = "org.mozilla.appservices:syncmanager:${Versions.mozilla_appservices}"

const val mozilla_push = "org.mozilla.appservices:push:${Versions.mozilla_appservices}"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ const val DB_NAME = "places.sqlite"
* Writer is always the same, as guaranteed by [PlacesApi].
*/
internal interface Connection : Closeable {
/**
* This should be removed. See: https://github.com/mozilla/application-services/issues/1877
*
* @return raw internal handle that could be used for referencing underlying [PlacesApi]. Use it with SyncManager.
*/
fun getHandle(): Long

fun reader(): PlacesReaderConnection
fun writer(): PlacesWriterConnection

Expand Down Expand Up @@ -57,6 +64,11 @@ internal object RustPlacesConnection : Connection {
cachedReader = api!!.openReader()
}

override fun getHandle(): Long {
check(api != null) { "must call init first" }
return api!!.getHandle()
}

override fun reader(): PlacesReaderConnection = synchronized(this) {
check(cachedReader != null) { "must call init first" }
return cachedReader!!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,13 @@ open class PlacesBookmarksStorage(context: Context) : PlacesStorage(context), Bo
}
}
}

/**
* This should be removed. See: https://github.com/mozilla/application-services/issues/1877
*
* @return raw internal handle that could be used for referencing underlying [PlacesApi]. Use it with SyncManager.
*/
override fun getHandle(): Long {
return places.getHandle()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package mozilla.components.browser.storage.sync

import android.content.Context
import kotlinx.coroutines.withContext
import mozilla.appservices.places.PlacesApi
import mozilla.appservices.places.VisitObservation
import mozilla.components.concept.storage.HistoryAutocompleteResult
import mozilla.components.concept.storage.HistoryStorage
Expand Down Expand Up @@ -177,4 +178,13 @@ open class PlacesHistoryStorage(context: Context) : PlacesStorage(context), Hist
}
}
}

/**
* This should be removed. See: https://github.com/mozilla/application-services/issues/1877
*
* @return raw internal handle that could be used for referencing underlying [PlacesApi]. Use it with SyncManager.
*/
override fun getHandle(): Long {
return places.getHandle()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,11 @@ class PlacesHistoryStorageTest {
fail()
}

override fun getHandle(): Long {
fail()
return 0L
}

override fun close() {
fail()
}
Expand Down Expand Up @@ -533,6 +538,11 @@ class PlacesHistoryStorageTest {

override fun syncBookmarks(syncInfo: SyncAuthInfo) {}

override fun getHandle(): Long {
fail()
return 0L
}

override fun close() {
fail()
}
Expand Down Expand Up @@ -565,6 +575,11 @@ class PlacesHistoryStorageTest {
fail()
}

override fun getHandle(): Long {
fail()
return 0L
}

override fun close() {
fail()
}
Expand Down Expand Up @@ -601,6 +616,11 @@ class PlacesHistoryStorageTest {
fail()
}

override fun getHandle(): Long {
fail()
return 0L
}

override fun close() {
fail()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@file:SuppressWarnings("TooManyFunctions")
package mozilla.components.concept.sync

import android.content.Context
import androidx.lifecycle.LifecycleOwner
import kotlinx.coroutines.Deferred
import mozilla.components.support.base.observer.Observable
Expand Down Expand Up @@ -51,9 +52,10 @@ interface DeviceConstellation : Observable<DeviceEventsObserver> {
/**
* Set name of the current device.
* @param name New device name.
* @param context An application context, used for updating internal caches.
* @return A [Deferred] that will be resolved with a success flag once operation is complete.
*/
fun setDeviceNameAsync(name: String): Deferred<Boolean>
fun setDeviceNameAsync(name: String, context: Context): Deferred<Boolean>

/**
* Set a [DevicePushSubscription] for the current device.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ interface SyncableStore {
* @return [SyncStatus] A status object describing how sync went.
*/
suspend fun sync(authInfo: SyncAuthInfo): SyncStatus

/**
* This should be removed. See: https://github.com/mozilla/application-services/issues/1877
*
* @return raw internal handle that could be used for referencing underlying [PlacesApi]. Use it with SyncManager.
*/
fun getHandle(): Long
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import mozilla.components.concept.sync.AuthType
import mozilla.components.service.fxa.FxaAuthData
import mozilla.components.service.fxa.SyncEngine
import mozilla.components.service.fxa.manager.FxaAccountManager
import mozilla.components.service.fxa.sync.toSyncEngines
import mozilla.components.service.fxa.toAuthType
import mozilla.components.support.base.feature.LifecycleAwareFeature
import mozilla.components.support.base.log.logger.Logger
Expand Down Expand Up @@ -248,19 +249,33 @@ class FxaWebChannelFeature(
return status
}

fun JSONArray.toStringList(): List<String> {
val result = mutableListOf<String>()
for (i in 0 until this.length()) {
result.add(this.getString(i))
}
return result
}

/**
* Handles the [COMMAND_OAUTH_LOGIN] event from the web-channel.
*/
private fun processOauthLoginCommand(accountManager: FxaAccountManager, payload: JSONObject): JSONObject? {
val authType: AuthType
val code: String
val state: String
val declinedEngines: List<String>?

try {
val data = payload.getJSONObject("data")
authType = data.getString("action").toAuthType()
code = data.getString("code")
state = data.getString("state")
declinedEngines = try {
data.getJSONArray("declinedSyncEngines").toStringList()
} catch (e: JSONException) {
null
}
} catch (e: JSONException) {
// TODO ideally, this should log to Sentry.
logger.error("Error while processing WebChannel oauth-login command", e)
Expand All @@ -270,7 +285,8 @@ class FxaWebChannelFeature(
accountManager.finishAuthenticationAsync(FxaAuthData(
authType = authType,
code = code,
state = state
state = state,
declinedEngines = declinedEngines?.toSyncEngines()
))

return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ class FxaWebChannelFeatureTest {
val messageHandler = argumentCaptor<MessageHandler>()
val responseToTheWebChannel = argumentCaptor<JSONObject>()
val port = mock<Port>()
val expectedEngines = setOf(SyncEngine.HISTORY)
val expectedEngines = setOf(SyncEngine.History)

WebExtensionController.installedExtensions[FxaWebChannelFeature.WEB_CHANNEL_EXTENSION_ID] = ext

Expand Down Expand Up @@ -201,7 +201,7 @@ class FxaWebChannelFeatureTest {
val messageHandler = argumentCaptor<MessageHandler>()
val responseToTheWebChannel = argumentCaptor<JSONObject>()
val port = mock<Port>()
val expectedEngines = setOf(SyncEngine.HISTORY)
val expectedEngines = setOf(SyncEngine.History)

WebExtensionController.installedExtensions[FxaWebChannelFeature.WEB_CHANNEL_EXTENSION_ID] = ext

Expand Down Expand Up @@ -250,7 +250,7 @@ class FxaWebChannelFeatureTest {
val messageHandler = argumentCaptor<MessageHandler>()
val responseToTheWebChannel = argumentCaptor<JSONObject>()
val port = mock<Port>()
val expectedEngines = setOf(SyncEngine.HISTORY, SyncEngine.BOOKMARKS, SyncEngine.PASSWORDS)
val expectedEngines = setOf(SyncEngine.History, SyncEngine.Bookmarks, SyncEngine.Passwords)

WebExtensionController.installedExtensions[FxaWebChannelFeature.WEB_CHANNEL_EXTENSION_ID] = ext

Expand Down Expand Up @@ -301,7 +301,7 @@ class FxaWebChannelFeatureTest {
val messageHandler = argumentCaptor<MessageHandler>()
val responseToTheWebChannel = argumentCaptor<JSONObject>()
val port = mock<Port>()
val expectedEngines = setOf(SyncEngine.HISTORY, SyncEngine.BOOKMARKS, SyncEngine.PASSWORDS)
val expectedEngines = setOf(SyncEngine.History, SyncEngine.Bookmarks, SyncEngine.Passwords)
val logoutDeferred = CompletableDeferred<Unit>()

WebExtensionController.installedExtensions[FxaWebChannelFeature.WEB_CHANNEL_EXTENSION_ID] = ext
Expand Down Expand Up @@ -363,7 +363,7 @@ class FxaWebChannelFeatureTest {
val messageHandler = argumentCaptor<MessageHandler>()
val responseToTheWebChannel = argumentCaptor<JSONObject>()
val port = mock<Port>()
val expectedEngines = setOf(SyncEngine.HISTORY, SyncEngine.BOOKMARKS, SyncEngine.PASSWORDS)
val expectedEngines = setOf(SyncEngine.History, SyncEngine.Bookmarks, SyncEngine.Passwords)
val logoutDeferred = CompletableDeferred<Unit>()

WebExtensionController.installedExtensions[FxaWebChannelFeature.WEB_CHANNEL_EXTENSION_ID] = ext
Expand Down Expand Up @@ -425,7 +425,7 @@ class FxaWebChannelFeatureTest {
val messageHandler = argumentCaptor<MessageHandler>()
val responseToTheWebChannel = argumentCaptor<JSONObject>()
val port = mock<Port>()
val expectedEngines = setOf(SyncEngine.HISTORY, SyncEngine.BOOKMARKS, SyncEngine.PASSWORDS)
val expectedEngines = setOf(SyncEngine.History, SyncEngine.Bookmarks, SyncEngine.Passwords)

WebExtensionController.installedExtensions[FxaWebChannelFeature.WEB_CHANNEL_EXTENSION_ID] = ext

Expand Down Expand Up @@ -477,7 +477,7 @@ class FxaWebChannelFeatureTest {
val messageHandler = argumentCaptor<MessageHandler>()
val responseToTheWebChannel = argumentCaptor<JSONObject>()
val port = mock<Port>()
val expectedEngines = setOf(SyncEngine.HISTORY, SyncEngine.BOOKMARKS, SyncEngine.PASSWORDS)
val expectedEngines = setOf(SyncEngine.History, SyncEngine.Bookmarks, SyncEngine.Passwords)

WebExtensionController.installedExtensions[FxaWebChannelFeature.WEB_CHANNEL_EXTENSION_ID] = ext

Expand Down Expand Up @@ -591,13 +591,13 @@ class FxaWebChannelFeatureTest {
messageHandler.value.onPortConnected(port)

// Action: signin
verifyOauthLogin("signin", AuthType.Signin, "fffs", "fsdf32", messageHandler.value, accountManager)
verifyOauthLogin("signin", AuthType.Signin, "fffs", "fsdf32", null, messageHandler.value, accountManager)
// Signup.
verifyOauthLogin("signup", AuthType.Signup, "anotherCode1", "anotherState2", messageHandler.value, accountManager)
verifyOauthLogin("signup", AuthType.Signup, "anotherCode1", "anotherState2", setOf(SyncEngine.Passwords), messageHandler.value, accountManager)
// Pairing.
verifyOauthLogin("pairing", AuthType.Pairing, "anotherCode2", "anotherState3", messageHandler.value, accountManager)
verifyOauthLogin("pairing", AuthType.Pairing, "anotherCode2", "anotherState3", null, messageHandler.value, accountManager)
// Some other action.
verifyOauthLogin("newAction", AuthType.OtherExternal("newAction"), "anotherCode3", "anotherState4", messageHandler.value, accountManager)
verifyOauthLogin("newAction", AuthType.OtherExternal("newAction"), "anotherCode3", "anotherState4", null, messageHandler.value, accountManager)
}

// Receiving an oauth-login message account manager refuses the request
Expand Down Expand Up @@ -628,13 +628,13 @@ class FxaWebChannelFeatureTest {
messageHandler.value.onPortConnected(port)

// Action: signin
verifyOauthLogin("signin", AuthType.Signin, "fffs", "fsdf32", messageHandler.value, accountManager)
verifyOauthLogin("signin", AuthType.Signin, "fffs", "fsdf32", setOf(SyncEngine.Passwords, SyncEngine.Bookmarks), messageHandler.value, accountManager)
// Signup.
verifyOauthLogin("signup", AuthType.Signup, "anotherCode1", "anotherState2", messageHandler.value, accountManager)
verifyOauthLogin("signup", AuthType.Signup, "anotherCode1", "anotherState2", null, messageHandler.value, accountManager)
// Pairing.
verifyOauthLogin("pairing", AuthType.Pairing, "anotherCode2", "anotherState3", messageHandler.value, accountManager)
verifyOauthLogin("pairing", AuthType.Pairing, "anotherCode2", "anotherState3", null, messageHandler.value, accountManager)
// Some other action.
verifyOauthLogin("newAction", AuthType.OtherExternal("newAction"), "anotherCode3", "anotherState4", messageHandler.value, accountManager)
verifyOauthLogin("newAction", AuthType.OtherExternal("newAction"), "anotherCode3", "anotherState4", null, messageHandler.value, accountManager)
}

// Receiving can-link-account returns 'ok=true' message (for now)
Expand All @@ -648,7 +648,7 @@ class FxaWebChannelFeatureTest {
val messageHandler = argumentCaptor<MessageHandler>()
val jsonFromWebChannel = argumentCaptor<JSONObject>()
val port = mock<Port>()
val expectedEngines = setOf(SyncEngine.HISTORY, SyncEngine.BOOKMARKS)
val expectedEngines = setOf(SyncEngine.History, SyncEngine.Bookmarks)

WebExtensionController.installedExtensions[FxaWebChannelFeature.WEB_CHANNEL_EXTENSION_ID] = ext

Expand Down Expand Up @@ -768,19 +768,20 @@ class FxaWebChannelFeatureTest {
.getBoolean("ok")
}

private fun verifyOauthLogin(action: String, expectedAuthType: AuthType, code: String, state: String, messageHandler: MessageHandler, accountManager: FxaAccountManager) {
val jsonToWebChannel = jsonOauthLogin(action, code, state)
private fun verifyOauthLogin(action: String, expectedAuthType: AuthType, code: String, state: String, declined: Set<SyncEngine>?, messageHandler: MessageHandler, accountManager: FxaAccountManager) {
val jsonToWebChannel = jsonOauthLogin(action, code, state, declined ?: emptySet())
messageHandler.onPortMessage(jsonToWebChannel, mock())

val expectedAuthData = FxaAuthData(
authType = expectedAuthType,
code = code,
state = state
state = state,
declinedEngines = declined
)
verify(accountManager).finishAuthenticationAsync(expectedAuthData)
}

private fun jsonOauthLogin(action: String, code: String, state: String): JSONObject {
private fun jsonOauthLogin(action: String, code: String, state: String, declined: Set<SyncEngine>): JSONObject {
return JSONObject(
"""{
"message":{
Expand All @@ -790,7 +791,8 @@ class FxaWebChannelFeatureTest {
"action":"$action",
"redirect":"urn:ietf:wg:oauth:2.0:oob:oauth-redirect-webchannel",
"code":"$code",
"state":"$state"
"state":"$state",
"declinedSyncEngines":[${declined.map { "${it.nativeName}," }}]
}
}
}""".trimIndent()
Expand Down
5 changes: 5 additions & 0 deletions components/service/firefox-accounts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ Useful companion components:
* [feature-accounts](https://github.com/mozilla-mobile/android-components/tree/master/components/feature/accounts), provides a `tabs` integration on top of `FxaAccountManager`, to handle display of web sign-in UI.
* [browser-storage-sync](https://github.com/mozilla-mobile/android-components/tree/master/components/browser/storage-sync), provides data storage layers compatible with Firefox Sync.

## Before using this component
Products sending telemetry and using this component *must request* a data-review following [this process](https://wiki.mozilla.org/Firefox/Data_Collection).
This component provides data collection using the [Glean SDK](https://mozilla.github.io/glean/book/index.html).
The list of metrics being collected is available in the [metrics documentation](../../support/telemetry/docs/metrics.md).

## Usage
### Setting up the dependency

Expand Down
2 changes: 2 additions & 0 deletions components/service/firefox-accounts/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ dependencies {

// Parts of this dependency are typealiase'd or are otherwise part of this module's public API.
api Dependencies.mozilla_fxa
implementation Dependencies.mozilla_sync_manager

// Observable is part of public API of the FxaAccountManager.
api project(':support-base')
implementation project(':support-sync-telemetry')
implementation project(':support-ktx')

implementation Dependencies.kotlin_stdlib
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,22 @@ data class SyncConfig(

/**
* Describes possible sync engines that device can support.
*
* @property nativeName Internally, Rust SyncManager represents engines as strings. Forward-compatibility
* with new engines is one of the reasons for this. E.g. during any sync, an engine may appear that we
* do not know about. At the public API level, we expose a concrete [SyncEngine] type to allow for more
* robust integrations. We do not expose "unknown" engines via our public API, but do handle them
* internally (by persisting their enabled/disabled status).
*/
enum class SyncEngine(val nativeName: String) {
HISTORY("history"),
BOOKMARKS("bookmarks"),
PASSWORDS("passwords"),
sealed class SyncEngine(val nativeName: String) {
object History : SyncEngine("history")
object Bookmarks : SyncEngine("bookmarks")
object Passwords : SyncEngine("passwords")
data class Other(val name: String) : SyncEngine(name)

/**
* This engine is used internally, but hidden from the public API because we don't fully support
* this data type right now.
*/
internal object Forms : SyncEngine("forms")
}
Loading

0 comments on commit 4ac03ab

Please sign in to comment.