Skip to content

Commit

Permalink
Improve link parsing mechanics
Browse files Browse the repository at this point in the history
Ensure all networks are available before processing to ensure we don't miss any potential address targets.
  • Loading branch information
Drew Carlson committed Jun 8, 2020
1 parent 4b3f789 commit 71819d4
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 133 deletions.
9 changes: 9 additions & 0 deletions app/src/main/java/com/breadwallet/breadbox/BreadBox.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
package com.breadwallet.breadbox

import com.breadwallet.crypto.Account
import com.breadwallet.crypto.Network

import com.breadwallet.crypto.Wallet
import com.breadwallet.crypto.System
Expand Down Expand Up @@ -74,6 +75,14 @@ interface BreadBox {
/** Emits the [WalletState] for the [Wallet] of [currencyCode]. */
fun walletState(currencyCode: String): Flow<WalletState>

/**
* Emits the [Network]s discovered by [System].
*
* Setting [whenDiscoveryComplete] to true delays the first
* emission until network discovery is complete.
*/
fun networks(whenDiscoveryComplete: Boolean = false): Flow<List<Network>>

/** Returns [System] when [isOpen] or null when it is not. */
fun getSystemUnsafe(): System?
}
27 changes: 23 additions & 4 deletions app/src/main/java/com/breadwallet/breadbox/CoreBreadBox.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import com.breadwallet.crypto.WalletManager
import com.breadwallet.crypto.WalletManagerState
import com.breadwallet.crypto.blockchaindb.BlockchainDb
import com.breadwallet.crypto.events.network.NetworkEvent
import com.breadwallet.crypto.events.system.SystemDiscoveredNetworksEvent
import com.breadwallet.crypto.events.system.SystemEvent
import com.breadwallet.crypto.events.system.SystemListener
import com.breadwallet.crypto.events.system.SystemNetworkAddedEvent
Expand Down Expand Up @@ -78,6 +79,7 @@ import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.launch
import java.io.File
import java.util.Locale
Expand Down Expand Up @@ -121,6 +123,7 @@ internal class CoreBreadBox(

private var networkManager: NetworkManager? = null

private val isDiscoveryComplete = AtomicBoolean(false)
private val _isOpen = AtomicBoolean(false)
override var isOpen: Boolean
get() = _isOpen.get()
Expand Down Expand Up @@ -351,6 +354,17 @@ internal class CoreBreadBox(
}
}

override fun networks(whenDiscoveryComplete: Boolean): Flow<List<Network>> =
system().transform {
if (whenDiscoveryComplete) {
if (isDiscoveryComplete.get()) {
emit(it.networks)
}
} else {
emit(it.networks)
}
}

override fun getSystemUnsafe(): System? = system

override fun handleWalletEvent(
Expand Down Expand Up @@ -448,11 +462,16 @@ internal class CoreBreadBox(
override fun handleNetworkEvent(system: System, network: Network, event: NetworkEvent) = Unit

override fun handleSystemEvent(system: System, event: SystemEvent) {
systemChannel.offer(Unit)
if (event is SystemNetworkAddedEvent) {
logDebug("Network '${event.network.name}' added.")
networkManager?.initializeNetwork(event.network)
when (event) {
is SystemNetworkAddedEvent -> {
logDebug("Network '${event.network.name}' added.")
networkManager?.initializeNetwork(event.network)
}
is SystemDiscoveredNetworksEvent -> {
isDiscoveryComplete.set(true)
}
}
systemChannel.offer(Unit)
}

override fun handleTransferEvent(
Expand Down
26 changes: 10 additions & 16 deletions app/src/main/java/com/breadwallet/tools/util/Link.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
package com.breadwallet.tools.util

import android.net.Uri
import com.breadwallet.app.BreadApp
import com.breadwallet.breadbox.BreadBox
import com.breadwallet.crypto.Address
import com.breadwallet.crypto.Key
Expand All @@ -38,7 +37,7 @@ import com.breadwallet.util.CryptoUriParser
import com.breadwallet.util.CurrencyCode
import com.platform.HTTPServer
import io.sweers.redacted.annotation.Redacted
import org.kodein.di.erased.instance
import kotlinx.coroutines.flow.first
import java.io.Serializable
import java.math.BigDecimal
import java.net.URLEncoder
Expand Down Expand Up @@ -104,22 +103,25 @@ sealed class Link {

/** Turn the url string into a [Link] or null if it is not supported. */
@Suppress("ComplexMethod")
fun String.asLink(): Link? {
suspend fun String.asLink(
breadBox: BreadBox,
uriParser: CryptoUriParser
): Link? {
if (isBlank()) return null
val uri = Uri.parse(this)
val (address, currencyCode) =
breadBox.getSystemUnsafe()
?.networks
?.mapNotNull { network ->
breadBox.networks(true)
.first()
.mapNotNull { network ->
Address.create(this, network)
.orNull()
?.let { address ->
address to network.currency.code
}
}
?.firstOrNull() ?: null to null
.firstOrNull() ?: null to null

return when {
isBlank() -> null
address != null && currencyCode != null ->
Link.CryptoRequestUrl(
currencyCode = currencyCode,
Expand All @@ -143,14 +145,6 @@ fun String.asLink(): Link? {
}
}

private val uriParser by lazy {
BreadApp.getKodeinInstance().instance<CryptoUriParser>()
}

private val breadBox by lazy {
BreadApp.getKodeinInstance().instance<BreadBox>()
}

fun CryptoRequest.asCryptoRequestUrl() =
Link.CryptoRequestUrl(
address = address,
Expand Down
205 changes: 114 additions & 91 deletions app/src/main/java/com/breadwallet/ui/navigation/RouterNavigator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
import com.breadwallet.R
import com.breadwallet.breadbox.BreadBox
import com.breadwallet.legacy.presenter.settings.NotificationSettingsController
import com.breadwallet.logger.logError
import com.breadwallet.protocols.messageexchange.MessageExchangeService
Expand Down Expand Up @@ -75,17 +76,33 @@ import com.breadwallet.ui.wallet.BrdWalletController
import com.breadwallet.ui.wallet.WalletController
import com.breadwallet.ui.web.WebController
import com.breadwallet.ui.writedownkey.WriteDownKeyController
import com.breadwallet.util.CryptoUriParser
import com.breadwallet.util.isBrd
import com.platform.HTTPServer
import com.platform.util.AppReviewPromptManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import org.kodein.di.KodeinAware
import org.kodein.di.android.closestKodein
import org.kodein.di.erased.instance
import java.util.Locale

@Suppress("TooManyFunctions")
class RouterNavigator(
private val routerProvider: () -> Router
) : NavigationTargetHandlerSpec {
) : NavigationTargetHandlerSpec, KodeinAware {

private val router get() = routerProvider()
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

override val kodein by closestKodein {
checkNotNull(router.activity?.applicationContext)
}

private val breadBox by instance<BreadBox>()
private val uriParser by instance<CryptoUriParser>()

fun navigateTo(target: NavigationTarget) = patch(target)

Expand Down Expand Up @@ -203,97 +220,13 @@ class RouterNavigator(
}

override fun deepLink(effect: NavigationTarget.DeepLink) {
val link = effect.url?.asLink() ?: effect.link
if (link == null) {
logError("Failed to parse url, ${effect.url}")
showLaunchScreen(effect.authenticated)
return
}
val isTopLogin = router.backstack.lastOrNull()?.controller() is LoginController
if (isTopLogin && effect.authenticated) {
router.popCurrentController()
}
when (link) {
is Link.CryptoRequestUrl -> {
val sendController = SendSheetController(link).asTransaction()
router.pushWithStackIfEmpty(sendController, effect.authenticated) {
listOf(
HomeController().asTransaction(),
WalletController(link.currencyCode).asTransaction(
popChangeHandler = HorizontalChangeHandler(),
pushChangeHandler = HorizontalChangeHandler()
),
sendController
)
}
}
is Link.BreadUrl.ScanQR -> {
val controller = ScannerController().asTransaction()
router.pushWithStackIfEmpty(controller, effect.authenticated) {
listOf(
HomeController().asTransaction(),
controller
)
}
}
is Link.ImportWallet -> {
val controller = ImportController(
privateKey = link.privateKey,
isPasswordProtected = link.passwordProtected
).asTransaction()
router.pushWithStackIfEmpty(controller, effect.authenticated) {
listOf(
HomeController().asTransaction(),
controller
)
}
}
is Link.PlatformUrl -> {
val controller = WebController(link.url).asTransaction()
router.pushWithStackIfEmpty(controller, effect.authenticated) {
listOf(
HomeController().asTransaction(),
controller
)
}
}
is Link.PlatformDebugUrl -> {
val context = router.activity!!.applicationContext
if (!link.webBundleUrl.isNullOrBlank()) {
ServerBundlesHelper.setWebPlatformDebugURL(link.webBundleUrl)
} else if (!link.webBundle.isNullOrBlank()) {
ServerBundlesHelper.setDebugBundle(
context,
ServerBundlesHelper.Type.WEB,
link.webBundle
)
}

showLaunchScreen(effect.authenticated)
}
Link.BreadUrl.ScanQR -> {
val controller = ScannerController().asTransaction()
router.pushWithStackIfEmpty(controller, effect.authenticated) {
listOf(
HomeController().asTransaction(),
controller
)
}
}
is Link.WalletPairUrl -> {
val context = router.activity!!.applicationContext
MessageExchangeService.enqueueWork(
context, MessageExchangeService.createIntent(
context,
MessageExchangeService.ACTION_REQUEST_TO_PAIR,
link.pairingMetaData
)
)
showLaunchScreen(effect.authenticated)
}
else -> {
logError("Failed to route deeplink, going Home.")
scope.launch(Dispatchers.Main) {
val link = effect.url?.asLink(breadBox, uriParser) ?: effect.link
if (link == null) {
logError("Failed to parse url, ${effect.url}")
showLaunchScreen(effect.authenticated)
} else {
processDeepLink(effect, link)
}
}
}
Expand Down Expand Up @@ -582,4 +515,94 @@ class RouterNavigator(
}
}
}

private fun processDeepLink(effect: NavigationTarget.DeepLink, link: Link) {
val isTopLogin = router.backstack.lastOrNull()?.controller() is LoginController
if (isTopLogin && effect.authenticated) {
router.popCurrentController()
}
when (link) {
is Link.CryptoRequestUrl -> {
val sendController = SendSheetController(link).asTransaction()
router.pushWithStackIfEmpty(sendController, effect.authenticated) {
listOf(
HomeController().asTransaction(),
WalletController(link.currencyCode).asTransaction(
popChangeHandler = HorizontalChangeHandler(),
pushChangeHandler = HorizontalChangeHandler()
),
sendController
)
}
}
is Link.BreadUrl.ScanQR -> {
val controller = ScannerController().asTransaction()
router.pushWithStackIfEmpty(controller, effect.authenticated) {
listOf(
HomeController().asTransaction(),
controller
)
}
}
is Link.ImportWallet -> {
val controller = ImportController(
privateKey = link.privateKey,
isPasswordProtected = link.passwordProtected
).asTransaction()
router.pushWithStackIfEmpty(controller, effect.authenticated) {
listOf(
HomeController().asTransaction(),
controller
)
}
}
is Link.PlatformUrl -> {
val controller = WebController(link.url).asTransaction()
router.pushWithStackIfEmpty(controller, effect.authenticated) {
listOf(
HomeController().asTransaction(),
controller
)
}
}
is Link.PlatformDebugUrl -> {
val context = router.activity!!.applicationContext
if (!link.webBundleUrl.isNullOrBlank()) {
ServerBundlesHelper.setWebPlatformDebugURL(link.webBundleUrl)
} else if (!link.webBundle.isNullOrBlank()) {
ServerBundlesHelper.setDebugBundle(
context,
ServerBundlesHelper.Type.WEB,
link.webBundle
)
}

showLaunchScreen(effect.authenticated)
}
Link.BreadUrl.ScanQR -> {
val controller = ScannerController().asTransaction()
router.pushWithStackIfEmpty(controller, effect.authenticated) {
listOf(
HomeController().asTransaction(),
controller
)
}
}
is Link.WalletPairUrl -> {
val context = router.activity!!.applicationContext
MessageExchangeService.enqueueWork(
context, MessageExchangeService.createIntent(
context,
MessageExchangeService.ACTION_REQUEST_TO_PAIR,
link.pairingMetaData
)
)
showLaunchScreen(effect.authenticated)
}
else -> {
logError("Failed to route deeplink, going Home.")
showLaunchScreen(effect.authenticated)
}
}
}
}
Loading

0 comments on commit 71819d4

Please sign in to comment.