From fcc6032ee0d2d7ce3f9cfa83f060d981f871ab98 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Wed, 2 Apr 2025 11:00:35 -0400 Subject: [PATCH] Generalize preventing WebSocket from connecting in various app states. --- .../ApplicationDependencyProvider.java | 11 ++++++-- .../DeviceTransferBlockingInterceptor.java | 7 +++--- .../net/SignalWebSocketHealthMonitor.kt | 10 +++++++- .../api/websocket/SignalWebSocket.kt | 25 ++++++++----------- 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java index 250de2c15c..ff6d86148b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java @@ -47,6 +47,7 @@ import org.thoughtcrime.securesms.jobs.TypingSendJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.megaphone.MegaphoneRepository; import org.thoughtcrime.securesms.messages.IncomingMessageObserver; +import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor; import org.thoughtcrime.securesms.net.SignalWebSocketHealthMonitor; import org.thoughtcrime.securesms.net.StandardUserAgentInterceptor; import org.thoughtcrime.securesms.notifications.MessageNotifier; @@ -339,7 +340,10 @@ public class ApplicationDependencyProvider implements AppDependencies.Provider { } }; - SignalWebSocket.AuthenticatedWebSocket webSocket = new SignalWebSocket.AuthenticatedWebSocket(authFactory, sleepTimer, TimeUnit.SECONDS.toMillis(10)); + SignalWebSocket.AuthenticatedWebSocket webSocket = new SignalWebSocket.AuthenticatedWebSocket(authFactory, + () -> !SignalStore.misc().isClientDeprecated() && !DeviceTransferBlockingInterceptor.getInstance().isBlockingNetwork(), + sleepTimer, + TimeUnit.SECONDS.toMillis(10)); if (AppForegroundObserver.isForegrounded()) { webSocket.registerKeepAliveToken(SignalWebSocket.FOREGROUND_KEEPALIVE); } @@ -372,7 +376,10 @@ public class ApplicationDependencyProvider implements AppDependencies.Provider { } }; - SignalWebSocket.UnauthenticatedWebSocket webSocket = new SignalWebSocket.UnauthenticatedWebSocket(unauthFactory, sleepTimer, TimeUnit.SECONDS.toMillis(10)); + SignalWebSocket.UnauthenticatedWebSocket webSocket = new SignalWebSocket.UnauthenticatedWebSocket(unauthFactory, + () -> !SignalStore.misc().isClientDeprecated() && !DeviceTransferBlockingInterceptor.getInstance().isBlockingNetwork(), + sleepTimer, + TimeUnit.SECONDS.toMillis(10)); if (AppForegroundObserver.isForegrounded()) { webSocket.registerKeepAliveToken(SignalWebSocket.FOREGROUND_KEEPALIVE); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/net/DeviceTransferBlockingInterceptor.java b/app/src/main/java/org/thoughtcrime/securesms/net/DeviceTransferBlockingInterceptor.java index 763945c5bd..12fec5fb5d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/net/DeviceTransferBlockingInterceptor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/net/DeviceTransferBlockingInterceptor.java @@ -5,7 +5,6 @@ import androidx.annotation.NonNull; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.dependencies.AppDependencies; import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.whispersystems.signalservice.api.websocket.SignalWebSocket; import java.io.IOException; @@ -49,15 +48,17 @@ public final class DeviceTransferBlockingInterceptor implements Interceptor { .build(); } + public boolean isBlockingNetwork() { + return blockNetworking; + } + public void blockNetwork() { blockNetworking = true; - SignalWebSocket.setCanConnect(false); AppDependencies.resetNetwork(); } public void unblockNetwork() { blockNetworking = false; - SignalWebSocket.setCanConnect(true); AppDependencies.startNetwork(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/net/SignalWebSocketHealthMonitor.kt b/app/src/main/java/org/thoughtcrime/securesms/net/SignalWebSocketHealthMonitor.kt index bb6718ad8a..328f2037be 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/net/SignalWebSocketHealthMonitor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/net/SignalWebSocketHealthMonitor.kt @@ -102,7 +102,15 @@ class SignalWebSocketHealthMonitor( } } - override fun onMessageError(status: Int, isIdentifiedWebSocket: Boolean) = Unit + override fun onMessageError(status: Int, isIdentifiedWebSocket: Boolean) { + executor.execute { + if (status == 499 && !SignalStore.misc.isClientDeprecated) { + Log.w(TAG, "Received 499. Client version is deprecated.", true) + SignalStore.misc.isClientDeprecated = true + webSocket?.forceNewWebSocket() + } + } + } private fun updateKeepAliveSenderStatus() { if (keepAliveSender == null && sendKeepAlives()) { diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/websocket/SignalWebSocket.kt b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/websocket/SignalWebSocket.kt index 052364d763..1e64b5dfa8 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/websocket/SignalWebSocket.kt +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/websocket/SignalWebSocket.kt @@ -33,6 +33,7 @@ import kotlin.time.Duration.Companion.milliseconds */ sealed class SignalWebSocket( private val connectionFactory: WebSocketFactory, + private val canConnect: CanConnect, val sleepTimer: SleepTimer, private val disconnectTimeout: Duration ) { @@ -43,14 +44,6 @@ sealed class SignalWebSocket( const val SERVER_DELIVERED_TIMESTAMP_HEADER = "X-Signal-Timestamp" const val FOREGROUND_KEEPALIVE = "Foregrounded" - - /** - * Set to false to prevent web sockets from connecting. After setting back to true the caller - * must manually start the sockets again by calling [connect]. - */ - @Volatile - @JvmStatic - var canConnect: Boolean = true } private var connection: WebSocketConnection? = null @@ -98,7 +91,7 @@ sealed class SignalWebSocket( @Synchronized @Throws(IOException::class) fun sendKeepAlive() { - if (canConnect) { + if (canConnect.canConnect()) { Log.v(TAG, "$connectionName keepAliveTokens: $keepAliveTokens") getWebSocket().sendKeepAlive() } @@ -119,7 +112,7 @@ sealed class SignalWebSocket( Log.v(TAG, "$connectionName Adding keepAliveToken: $token, current: $keepAliveTokens") } - if (canConnect) { + if (canConnect.canConnect()) { try { connect() } catch (e: WebSocketUnavailableException) { @@ -169,7 +162,7 @@ sealed class SignalWebSocket( @Synchronized @Throws(WebSocketUnavailableException::class) protected fun getWebSocket(): WebSocketConnection { - if (!canConnect) { + if (!canConnect.canConnect()) { throw WebSocketUnavailableException() } @@ -203,7 +196,7 @@ sealed class SignalWebSocket( @Synchronized fun forceNewWebSocket() { - Log.i(TAG, "$connectionName Forcing new WebSocket, canConnect: $canConnect") + Log.i(TAG, "$connectionName Forcing new WebSocket, canConnect: ${canConnect.canConnect()}") disconnect() } @@ -282,7 +275,7 @@ sealed class SignalWebSocket( /** * WebSocket type for communicating with the server without authenticating. Also known as "unidentified". */ - class UnauthenticatedWebSocket(connectionFactory: WebSocketFactory, sleepTimer: SleepTimer, disconnectTimeoutMs: Long) : SignalWebSocket(connectionFactory, sleepTimer, disconnectTimeoutMs.milliseconds) { + class UnauthenticatedWebSocket(connectionFactory: WebSocketFactory, canConnect: CanConnect, sleepTimer: SleepTimer, disconnectTimeoutMs: Long) : SignalWebSocket(connectionFactory, canConnect, sleepTimer, disconnectTimeoutMs.milliseconds) { fun request(requestMessage: WebSocketRequestMessage, sealedSenderAccess: SealedSenderAccess): Single { val headers: MutableList = requestMessage.headers.toMutableList() headers.add(sealedSenderAccess.header) @@ -312,7 +305,7 @@ sealed class SignalWebSocket( /** * WebSocket type for communicating with the server with authentication. Also known as "identified". */ - class AuthenticatedWebSocket(connectionFactory: WebSocketFactory, sleepTimer: SleepTimer, disconnectTimeoutMs: Long) : SignalWebSocket(connectionFactory, sleepTimer, disconnectTimeoutMs.milliseconds) { + class AuthenticatedWebSocket(connectionFactory: WebSocketFactory, canConnect: CanConnect, sleepTimer: SleepTimer, disconnectTimeoutMs: Long) : SignalWebSocket(connectionFactory, canConnect, sleepTimer, disconnectTimeoutMs.milliseconds) { /** * The reads a batch of messages off of the websocket. @@ -421,4 +414,8 @@ sealed class SignalWebSocket( fun onMessageBatch(envelopeResponses: List) } } + + fun interface CanConnect { + fun canConnect(): Boolean + } }