Skip to content

Commit

Permalink
Add connection restart
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubuid committed Jun 18, 2024
1 parent aa747f2 commit 681161c
Show file tree
Hide file tree
Showing 14 changed files with 202 additions and 220 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import com.walletconnect.android.relay.RelayConnectionInterface
import com.walletconnect.android.utils.isValidRelayServerUrl
import com.walletconnect.android.utils.plantTimber
import com.walletconnect.android.utils.projectId
import com.walletconnect.android.utils.toCommonConnectionType
import com.walletconnect.android.verify.client.VerifyClient
import com.walletconnect.android.verify.client.VerifyInterface
import org.koin.android.ext.koin.androidContext
Expand Down Expand Up @@ -83,13 +82,13 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter
modules(
module { single { ProjectId(relayServerUrl.projectId()) } },
module { single(named(AndroidCommonDITags.TELEMETRY_ENABLED)) { TelemetryEnabled(telemetryEnabled) } },
coreAndroidNetworkModule(relayServerUrl, connectionType.toCommonConnectionType(), BuildConfig.SDK_VERSION, networkClientTimeout, bundleId),
coreAndroidNetworkModule(relayServerUrl, connectionType, BuildConfig.SDK_VERSION, networkClientTimeout, bundleId),
coreCommonModule(),
coreCryptoModule(),
)

if (relay == null) {
Relay.initialize { error -> onError(Core.Model.Error(error)) }
Relay.initialize(connectionType) { error -> onError(Core.Model.Error(error)) }
}

modules(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,20 @@ package com.walletconnect.android.internal.common.connection

import com.tinder.scarlet.Lifecycle
import com.tinder.scarlet.lifecycle.LifecycleRegistry
import com.walletconnect.android.internal.common.scope
import com.walletconnect.foundation.network.data.ConnectionController
import com.walletconnect.foundation.network.data.ConnectionEvent
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach

internal class ManualConnectionLifecycle(
connectionController: ConnectionController,
private val lifecycleRegistry: LifecycleRegistry,
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(),
) : Lifecycle by lifecycleRegistry {
fun connect() {
lifecycleRegistry.onNext(Lifecycle.State.Started)
}

fun disconnect() {
lifecycleRegistry.onNext(Lifecycle.State.Stopped.WithReason())
}

init {
if (connectionController is ConnectionController.Manual) {
connectionController.connectionEventFlow
.onEach { event ->
when (event) {
ConnectionEvent.CONNECT -> lifecycleRegistry.onNext(Lifecycle.State.Started)
ConnectionEvent.DISCONNECT -> lifecycleRegistry.onNext(Lifecycle.State.Stopped.WithReason())
}
}
.launchIn(scope)
}
fun restart() {
lifecycleRegistry.onNext(Lifecycle.State.Stopped.WithReason())
lifecycleRegistry.onNext(Lifecycle.State.Started)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ enum class AndroidCommonDITags {
RELAY_SERVICE,
SCARLET,
MSG_ADAPTER,
CONNECTION_CONTROLLER,
LIFECYCLE,
MANUAL_CONNECTION_LIFECYCLE,
AUTOMATIC_CONNECTION_LIFECYCLE,
LOGGER,
CONNECTIVITY_STATE,
PUSH_RETROFIT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import android.net.Uri
import android.os.Build
import com.pandulapeter.beagle.logOkHttp.BeagleOkHttpLogger
import com.squareup.moshi.Moshi
import com.tinder.scarlet.Lifecycle
import com.tinder.scarlet.Scarlet
import com.tinder.scarlet.lifecycle.LifecycleRegistry
import com.tinder.scarlet.lifecycle.android.AndroidLifecycle
import com.tinder.scarlet.messageadapter.moshi.MoshiMessageAdapter
import com.tinder.scarlet.retry.LinearBackoffStrategy
Expand All @@ -16,7 +16,6 @@ import com.walletconnect.android.internal.common.connection.ManualConnectionLife
import com.walletconnect.android.internal.common.jwt.clientid.GenerateJwtStoreClientIdUseCase
import com.walletconnect.android.relay.ConnectionType
import com.walletconnect.android.relay.NetworkClientTimeout
import com.walletconnect.foundation.network.data.ConnectionController
import com.walletconnect.foundation.network.data.adapter.FlowStreamAdapter
import com.walletconnect.foundation.network.data.service.RelayService
import okhttp3.Authenticator
Expand All @@ -26,6 +25,7 @@ import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import org.koin.android.ext.koin.androidApplication
import org.koin.core.qualifier.named
import org.koin.core.scope.Scope
import org.koin.dsl.module
import java.io.IOException
import java.net.SocketTimeoutException
Expand Down Expand Up @@ -93,6 +93,7 @@ fun coreAndroidNetworkModule(serverUrl: String, connectionType: ConnectionType,
request = request.newBuilder().url(newHttpUrl).build()
return@Interceptor chain.proceed(request)
}

else -> {
throw e
}
Expand Down Expand Up @@ -143,20 +144,12 @@ fun coreAndroidNetworkModule(serverUrl: String, connectionType: ConnectionType,

single(named(AndroidCommonDITags.MSG_ADAPTER)) { MoshiMessageAdapter.Factory(get<Moshi.Builder>(named(AndroidCommonDITags.MOSHI)).build()) }

single(named(AndroidCommonDITags.CONNECTION_CONTROLLER)) {
if (connectionType == ConnectionType.MANUAL) {
ConnectionController.Manual()
} else {
ConnectionController.Automatic
}
single<ManualConnectionLifecycle>(named(AndroidCommonDITags.MANUAL_CONNECTION_LIFECYCLE)) {
ManualConnectionLifecycle()
}

single(named(AndroidCommonDITags.LIFECYCLE)) {
if (connectionType == ConnectionType.MANUAL) {
ManualConnectionLifecycle(get(named(AndroidCommonDITags.CONNECTION_CONTROLLER)), LifecycleRegistry())
} else {
AndroidLifecycle.ofApplicationForeground(androidApplication())
}
single<Lifecycle>(named(AndroidCommonDITags.AUTOMATIC_CONNECTION_LIFECYCLE)) {
AndroidLifecycle.ofApplicationForeground(androidApplication())
}

single { LinearBackoffStrategy(TimeUnit.SECONDS.toMillis(DEFAULT_BACKOFF_SECONDS)) }
Expand All @@ -167,7 +160,7 @@ fun coreAndroidNetworkModule(serverUrl: String, connectionType: ConnectionType,
Scarlet.Builder()
.backoffStrategy(get<LinearBackoffStrategy>())
.webSocketFactory(get<OkHttpClient>(named(AndroidCommonDITags.OK_HTTP)).newWebSocketFactory(get<String>(named(AndroidCommonDITags.RELAY_URL))))
.lifecycle(get(named(AndroidCommonDITags.LIFECYCLE)))
.lifecycle(getLifecycle(connectionType))
.addMessageAdapterFactory(get<MoshiMessageAdapter.Factory>(named(AndroidCommonDITags.MSG_ADAPTER)))
.addStreamAdapterFactory(get<FlowStreamAdapter.Factory>())
.build()
Expand All @@ -180,4 +173,11 @@ fun coreAndroidNetworkModule(serverUrl: String, connectionType: ConnectionType,
single(named(AndroidCommonDITags.CONNECTIVITY_STATE)) {
ConnectivityState(androidApplication())
}
}
}

private fun Scope.getLifecycle(connectionType: ConnectionType) =
if (connectionType == ConnectionType.MANUAL) {
get<ManualConnectionLifecycle>(named(AndroidCommonDITags.MANUAL_CONNECTION_LIFECYCLE))
} else {
get<Lifecycle>(named(AndroidCommonDITags.AUTOMATIC_CONNECTION_LIFECYCLE))
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ package com.walletconnect.android.relay

import com.walletconnect.android.Core
import com.walletconnect.android.internal.common.connection.ConnectivityState
import com.walletconnect.android.internal.common.connection.ManualConnectionLifecycle
import com.walletconnect.android.internal.common.di.AndroidCommonDITags
import com.walletconnect.android.internal.common.exception.WRONG_CONNECTION_TYPE
import com.walletconnect.android.internal.common.scope
import com.walletconnect.android.internal.common.wcKoinApp
import com.walletconnect.android.utils.toWalletConnectException
import com.walletconnect.foundation.network.BaseRelayClient
import com.walletconnect.foundation.network.data.ConnectionController
import com.walletconnect.foundation.network.model.Relay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
Expand All @@ -24,80 +24,95 @@ import org.koin.core.KoinApplication
import org.koin.core.qualifier.named

class RelayClient(private val koinApp: KoinApplication = wcKoinApp) : BaseRelayClient(), RelayConnectionInterface {
private val connectionController: ConnectionController by lazy { koinApp.koin.get(named(AndroidCommonDITags.CONNECTION_CONTROLLER)) }
private val networkState: ConnectivityState by lazy { koinApp.koin.get(named(AndroidCommonDITags.CONNECTIVITY_STATE)) }
override val isNetworkAvailable: StateFlow<Boolean?> by lazy { networkState.isAvailable }
private val _wssConnectionState: MutableStateFlow<WSSConnectionState> = MutableStateFlow(WSSConnectionState.Disconnected.ConnectionClosed())
override val wssConnectionState: StateFlow<WSSConnectionState> = _wssConnectionState
private val manualConnection: ManualConnectionLifecycle by lazy { koinApp.koin.get(named(AndroidCommonDITags.MANUAL_CONNECTION_LIFECYCLE)) }
private val networkState: ConnectivityState by lazy { koinApp.koin.get(named(AndroidCommonDITags.CONNECTIVITY_STATE)) }
override val isNetworkAvailable: StateFlow<Boolean?> by lazy { networkState.isAvailable }
private val _wssConnectionState: MutableStateFlow<WSSConnectionState> = MutableStateFlow(WSSConnectionState.Disconnected.ConnectionClosed())
override val wssConnectionState: StateFlow<WSSConnectionState> = _wssConnectionState
private lateinit var connectionType: ConnectionType

@JvmSynthetic
fun initialize(onError: (Throwable) -> Unit) {
logger = koinApp.koin.get(named(AndroidCommonDITags.LOGGER))
relayService = koinApp.koin.get(named(AndroidCommonDITags.RELAY_SERVICE))
collectConnectionInitializationErrors { error -> onError(error) }
monitorConnectionState()
observeResults()
}
@JvmSynthetic
fun initialize(connectionType: ConnectionType, onError: (Throwable) -> Unit) {
this.connectionType = connectionType
logger = koinApp.koin.get(named(AndroidCommonDITags.LOGGER))
relayService = koinApp.koin.get(named(AndroidCommonDITags.RELAY_SERVICE))
collectConnectionInitializationErrors { error -> onError(error) }
monitorConnectionState()
observeResults()
}

private fun collectConnectionInitializationErrors(onError: (Throwable) -> Unit) {
scope.launch {
supervisorScope {
eventsFlow
.takeWhile { event ->
if (event is Relay.Model.Event.OnConnectionFailed) {
onError(event.throwable.toWalletConnectException)
}
private fun collectConnectionInitializationErrors(onError: (Throwable) -> Unit) {
scope.launch {
supervisorScope {
eventsFlow
.takeWhile { event ->
if (event is Relay.Model.Event.OnConnectionFailed) {
onError(event.throwable.toWalletConnectException)
}

event !is Relay.Model.Event.OnConnectionOpened<*>
}.collect()
}
}
}
event !is Relay.Model.Event.OnConnectionOpened<*>
}.collect()
}
}
}

private fun monitorConnectionState() {
eventsFlow
.onEach { event: Relay.Model.Event -> setIsWSSConnectionOpened(event) }
.launchIn(scope)
}
private fun monitorConnectionState() {
eventsFlow
.onEach { event: Relay.Model.Event -> setIsWSSConnectionOpened(event) }
.launchIn(scope)
}

private fun setIsWSSConnectionOpened(event: Relay.Model.Event) {
when {
event is Relay.Model.Event.OnConnectionOpened<*> && _wssConnectionState.value is WSSConnectionState.Disconnected ->
_wssConnectionState.value = WSSConnectionState.Connected
private fun setIsWSSConnectionOpened(event: Relay.Model.Event) {
when {
event is Relay.Model.Event.OnConnectionOpened<*> && _wssConnectionState.value is WSSConnectionState.Disconnected ->
_wssConnectionState.value = WSSConnectionState.Connected

event is Relay.Model.Event.OnConnectionFailed && _wssConnectionState.value is WSSConnectionState.Connected ->
_wssConnectionState.value = WSSConnectionState.Disconnected.ConnectionFailed(event.throwable)
event is Relay.Model.Event.OnConnectionFailed && _wssConnectionState.value is WSSConnectionState.Connected ->
_wssConnectionState.value = WSSConnectionState.Disconnected.ConnectionFailed(event.throwable)

event is Relay.Model.Event.OnConnectionClosed && _wssConnectionState.value is WSSConnectionState.Connected ->
_wssConnectionState.value = WSSConnectionState.Disconnected.ConnectionClosed("Connection closed: ${event.shutdownReason.reason} ${event.shutdownReason.code}")
}
}
event is Relay.Model.Event.OnConnectionClosed && _wssConnectionState.value is WSSConnectionState.Connected ->
_wssConnectionState.value = WSSConnectionState.Disconnected.ConnectionClosed("Connection closed: ${event.shutdownReason.reason} ${event.shutdownReason.code}")
}
}

override fun connect(onErrorModel: (Core.Model.Error) -> Unit, onError: (String) -> Unit) {
when (connectionController) {
is ConnectionController.Automatic -> onError(WRONG_CONNECTION_TYPE)
is ConnectionController.Manual -> (connectionController as ConnectionController.Manual).connect()
}
}
override fun connect(onError: (Core.Model.Error) -> Unit) {
when (connectionType) {
ConnectionType.AUTOMATIC -> onError(Core.Model.Error(IllegalStateException(WRONG_CONNECTION_TYPE)))
ConnectionType.MANUAL -> manualConnection.connect()
}
}

override fun connect(onError: (Core.Model.Error) -> Unit) {
when (connectionController) {
is ConnectionController.Automatic -> onError(Core.Model.Error(IllegalStateException(WRONG_CONNECTION_TYPE)))
is ConnectionController.Manual -> (connectionController as ConnectionController.Manual).connect()
}
}
override fun disconnect(onError: (Core.Model.Error) -> Unit) {
when (connectionType) {
ConnectionType.AUTOMATIC -> onError(Core.Model.Error(IllegalStateException(WRONG_CONNECTION_TYPE)))
ConnectionType.MANUAL -> manualConnection.disconnect()
}
}

override fun disconnect(onErrorModel: (Core.Model.Error) -> Unit, onError: (String) -> Unit) {
when (connectionController) {
is ConnectionController.Automatic -> onError(WRONG_CONNECTION_TYPE)
is ConnectionController.Manual -> (connectionController as ConnectionController.Manual).disconnect()
}
}
override fun restart(onError: (Core.Model.Error) -> Unit) {
try {
when (connectionType) {
ConnectionType.AUTOMATIC -> onError(Core.Model.Error(IllegalStateException(WRONG_CONNECTION_TYPE)))
ConnectionType.MANUAL -> manualConnection.restart()
}
} catch (e: Exception) {
onError(Core.Model.Error(e))
}
}

override fun disconnect(onError: (Core.Model.Error) -> Unit) {
when (connectionController) {
is ConnectionController.Automatic -> onError(Core.Model.Error(IllegalStateException(WRONG_CONNECTION_TYPE)))
is ConnectionController.Manual -> (connectionController as ConnectionController.Manual).disconnect()
}
}
@Deprecated("This has become deprecate in favor of the onError returning Core.Model.Error", replaceWith = ReplaceWith("this.connect(onErrorModel)"))
override fun connect(onErrorModel: (Core.Model.Error) -> Unit, onError: (String) -> Unit) {
when (connectionType) {
ConnectionType.AUTOMATIC -> onError(WRONG_CONNECTION_TYPE)
ConnectionType.MANUAL -> manualConnection.connect()
}
}

@Deprecated("This has become deprecate in favor of the onError returning Core.Model.Error", replaceWith = ReplaceWith("this.disconnect(onErrorModel)"))
override fun disconnect(onErrorModel: (Core.Model.Error) -> Unit, onError: (String) -> Unit) {
when (connectionType) {
ConnectionType.AUTOMATIC -> onError(WRONG_CONNECTION_TYPE)
ConnectionType.MANUAL -> manualConnection.disconnect()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ interface RelayConnectionInterface : RelayInterface {
@Deprecated("This has become deprecate in favor of the onError returning Core.Model.Error", ReplaceWith("this.disconnect(onErrorModel)"))
fun disconnect(onErrorModel: (Core.Model.Error) -> Unit = {}, onError: (String) -> Unit)
fun disconnect(onError: (Core.Model.Error) -> Unit)

fun restart(onError: (Core.Model.Error) -> Unit)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import com.walletconnect.android.internal.common.exception.ProjectIdDoesNotExist
import com.walletconnect.android.internal.common.exception.UnableToConnectToWebsocketException
import com.walletconnect.android.internal.common.exception.WalletConnectException
import com.walletconnect.android.internal.common.model.AppMetaData
import com.walletconnect.android.relay.ConnectionType
import com.walletconnect.utils.Empty
import java.net.HttpURLConnection

Expand All @@ -19,13 +18,6 @@ internal fun String.strippedUrl() = Uri.parse(this).run {
this@run.scheme + "://" + this@run.authority
}

@JvmSynthetic
internal fun ConnectionType.toCommonConnectionType(): ConnectionType =
when (this) {
ConnectionType.AUTOMATIC -> ConnectionType.AUTOMATIC
ConnectionType.MANUAL -> ConnectionType.MANUAL
}

@JvmSynthetic
internal fun String.isValidRelayServerUrl(): Boolean {
return this.isNotBlank() && Uri.parse(this)?.let { relayUrl ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import com.tinder.scarlet.WebSocket
import com.walletconnect.android.internal.common.connection.ConnectivityState
import com.walletconnect.android.internal.common.di.AndroidCommonDITags
import com.walletconnect.android.internal.common.scope
import com.walletconnect.android.relay.ConnectionType
import com.walletconnect.android.relay.RelayClient
import com.walletconnect.foundation.network.data.ConnectionController
import com.walletconnect.foundation.network.data.service.RelayService
import com.walletconnect.foundation.util.Logger
import io.mockk.clearAllMocks
Expand Down Expand Up @@ -35,7 +35,6 @@ class RelayClientTests {
private lateinit var relayClient: RelayClient
private val mockRelayService = mockk<RelayService>(relaxed = true)
private val mockLogger = mockk<Logger>(relaxed = true)
private val mockConnectionController = mockk<ConnectionController>(relaxed = true)
private val mockNetworkState = mockk<ConnectivityState>(relaxed = true)
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
Expand All @@ -47,7 +46,6 @@ class RelayClientTests {
modules(module {
single(named(AndroidCommonDITags.RELAY_SERVICE)) { mockRelayService }
single(named(AndroidCommonDITags.LOGGER)) { mockLogger }
single(named(AndroidCommonDITags.CONNECTION_CONTROLLER)) { mockConnectionController }
single(named(AndroidCommonDITags.CONNECTIVITY_STATE)) { mockNetworkState }
})
}
Expand All @@ -73,7 +71,7 @@ class RelayClientTests {
emit(WebSocket.Event.OnConnectionFailed(Throwable("Network failure2")))
}

relayClient.initialize { error ->
relayClient.initialize(ConnectionType.MANUAL) { error ->
assertEquals(
"Error while connecting, please check your Internet connection or contact support: java.lang.Throwable: Network failure",
error.message
Expand Down
Loading

0 comments on commit 681161c

Please sign in to comment.