mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-15 07:28:30 +00:00
Convert remote config apis to WebSocket.
This commit is contained in:
@@ -57,6 +57,7 @@ import org.whispersystems.signalservice.api.profiles.ProfileApi
|
||||
import org.whispersystems.signalservice.api.provisioning.ProvisioningApi
|
||||
import org.whispersystems.signalservice.api.ratelimit.RateLimitChallengeApi
|
||||
import org.whispersystems.signalservice.api.registration.RegistrationApi
|
||||
import org.whispersystems.signalservice.api.remoteconfig.RemoteConfigApi
|
||||
import org.whispersystems.signalservice.api.services.DonationsService
|
||||
import org.whispersystems.signalservice.api.services.ProfileService
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceApi
|
||||
@@ -346,6 +347,9 @@ object AppDependencies {
|
||||
val profileApi: ProfileApi
|
||||
get() = networkModule.profileApi
|
||||
|
||||
val remoteConfigApi: RemoteConfigApi
|
||||
get() = networkModule.remoteConfigApi
|
||||
|
||||
@JvmStatic
|
||||
val okHttpClient: OkHttpClient
|
||||
get() = networkModule.okHttpClient
|
||||
@@ -426,5 +430,6 @@ object AppDependencies {
|
||||
fun provideProvisioningApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket): ProvisioningApi
|
||||
fun provideCertificateApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket): CertificateApi
|
||||
fun provideProfileApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket, pushServiceSocket: PushServiceSocket): ProfileApi
|
||||
fun provideRemoteConfigApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket): RemoteConfigApi
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +99,7 @@ import org.whispersystems.signalservice.api.push.ServiceId.ACI;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
|
||||
import org.whispersystems.signalservice.api.ratelimit.RateLimitChallengeApi;
|
||||
import org.whispersystems.signalservice.api.registration.RegistrationApi;
|
||||
import org.whispersystems.signalservice.api.remoteconfig.RemoteConfigApi;
|
||||
import org.whispersystems.signalservice.api.services.DonationsService;
|
||||
import org.whispersystems.signalservice.api.services.ProfileService;
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceApi;
|
||||
@@ -552,6 +553,11 @@ public class ApplicationDependencyProvider implements AppDependencies.Provider {
|
||||
return new ProfileApi(authWebSocket, pushServiceSocket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull RemoteConfigApi provideRemoteConfigApi(@NonNull SignalWebSocket.AuthenticatedWebSocket authWebSocket) {
|
||||
return new RemoteConfigApi(authWebSocket);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static class DynamicCredentialsProvider implements CredentialsProvider {
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ import org.whispersystems.signalservice.api.provisioning.ProvisioningApi
|
||||
import org.whispersystems.signalservice.api.push.TrustStore
|
||||
import org.whispersystems.signalservice.api.ratelimit.RateLimitChallengeApi
|
||||
import org.whispersystems.signalservice.api.registration.RegistrationApi
|
||||
import org.whispersystems.signalservice.api.remoteconfig.RemoteConfigApi
|
||||
import org.whispersystems.signalservice.api.services.DonationsService
|
||||
import org.whispersystems.signalservice.api.services.ProfileService
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceApi
|
||||
@@ -208,6 +209,10 @@ class NetworkDependenciesModule(
|
||||
provider.provideProfileApi(authWebSocket, pushServiceSocket)
|
||||
}
|
||||
|
||||
val remoteConfigApi: RemoteConfigApi by lazy {
|
||||
provider.provideRemoteConfigApi(authWebSocket)
|
||||
}
|
||||
|
||||
val okHttpClient: OkHttpClient by lazy {
|
||||
OkHttpClient.Builder()
|
||||
.addInterceptor(StandardUserAgentInterceptor())
|
||||
|
||||
@@ -7,15 +7,14 @@ package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.BuildConfig
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.net.SignalNetwork
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.whispersystems.signalservice.api.NetworkResult
|
||||
import org.whispersystems.signalservice.api.RemoteConfigResult
|
||||
import org.whispersystems.signalservice.api.remoteconfig.RemoteConfigResult
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
/**
|
||||
* If we have reason to believe a build is expired, we run this job to double-check by fetching the server time. This prevents false positives from people
|
||||
@@ -57,13 +56,9 @@ class BuildExpirationConfirmationJob private constructor(params: Parameters) : J
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
val result: NetworkResult<RemoteConfigResult> = NetworkResult.fromFetch {
|
||||
AppDependencies.signalServiceAccountManager.remoteConfig
|
||||
}
|
||||
|
||||
return when (result) {
|
||||
return when (val result: NetworkResult<RemoteConfigResult> = SignalNetwork.remoteConfig.getRemoteConfig()) {
|
||||
is NetworkResult.Success -> {
|
||||
val serverTimeMs = result.result.serverEpochTimeSeconds.seconds.inWholeMilliseconds
|
||||
val serverTimeMs = result.result.serverEpochTimeMilliseconds
|
||||
SignalStore.misc.setLastKnownServerTime(serverTimeMs, System.currentTimeMillis())
|
||||
|
||||
if (Util.getTimeUntilBuildExpiry(serverTimeMs) <= 0) {
|
||||
|
||||
@@ -8,8 +8,11 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.net.SignalNetwork;
|
||||
import org.thoughtcrime.securesms.util.ExceptionHelper;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.whispersystems.signalservice.api.RemoteConfigResult;
|
||||
import org.whispersystems.signalservice.api.NetworkResultUtil;
|
||||
import org.whispersystems.signalservice.api.remoteconfig.RemoteConfigResult;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -51,14 +54,14 @@ public class RemoteConfigRefreshJob extends BaseJob {
|
||||
return;
|
||||
}
|
||||
|
||||
RemoteConfigResult result = AppDependencies.getSignalServiceAccountManager().getRemoteConfig();
|
||||
RemoteConfigResult result = NetworkResultUtil.toBasicLegacy(SignalNetwork.remoteConfig().getRemoteConfig());
|
||||
RemoteConfig.update(result.getConfig());
|
||||
SignalStore.misc().setLastKnownServerTime(TimeUnit.SECONDS.toMillis(result.getServerEpochTimeSeconds()), System.currentTimeMillis());
|
||||
SignalStore.misc().setLastKnownServerTime(result.getServerEpochTimeMilliseconds(), System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onShouldRetry(@NonNull Exception e) {
|
||||
return e instanceof PushNetworkException;
|
||||
return ExceptionHelper.isRetryableIOException(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.whispersystems.signalservice.api.payments.PaymentsApi
|
||||
import org.whispersystems.signalservice.api.profiles.ProfileApi
|
||||
import org.whispersystems.signalservice.api.provisioning.ProvisioningApi
|
||||
import org.whispersystems.signalservice.api.ratelimit.RateLimitChallengeApi
|
||||
import org.whispersystems.signalservice.api.remoteconfig.RemoteConfigApi
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceApi
|
||||
import org.whispersystems.signalservice.api.username.UsernameApi
|
||||
|
||||
@@ -81,6 +82,11 @@ object SignalNetwork {
|
||||
val rateLimitChallenge: RateLimitChallengeApi
|
||||
get() = AppDependencies.rateLimitChallengeApi
|
||||
|
||||
@JvmStatic
|
||||
@get:JvmName("remoteConfig")
|
||||
val remoteConfig: RemoteConfigApi
|
||||
get() = AppDependencies.remoteConfigApi
|
||||
|
||||
val storageService: StorageServiceApi
|
||||
get() = AppDependencies.storageServiceApi
|
||||
|
||||
|
||||
@@ -15,9 +15,11 @@ import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob
|
||||
import org.thoughtcrime.securesms.jobs.Svr3MirrorJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.messageprocessingalarm.RoutineMessageFetchReceiver
|
||||
import org.thoughtcrime.securesms.net.SignalNetwork
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig.Config
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig.remoteBoolean
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig.remoteValue
|
||||
import org.whispersystems.signalservice.api.NetworkResultUtil
|
||||
import java.io.IOException
|
||||
import java.util.TreeMap
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
@@ -90,7 +92,7 @@ object RemoteConfig {
|
||||
@WorkerThread
|
||||
@Throws(IOException::class)
|
||||
fun refreshSync() {
|
||||
val result = AppDependencies.signalServiceAccountManager.getRemoteConfig()
|
||||
val result = NetworkResultUtil.toBasicLegacy(SignalNetwork.remoteConfig.getRemoteConfig())
|
||||
update(result.config)
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ import org.whispersystems.signalservice.api.profiles.ProfileApi
|
||||
import org.whispersystems.signalservice.api.provisioning.ProvisioningApi
|
||||
import org.whispersystems.signalservice.api.ratelimit.RateLimitChallengeApi
|
||||
import org.whispersystems.signalservice.api.registration.RegistrationApi
|
||||
import org.whispersystems.signalservice.api.remoteconfig.RemoteConfigApi
|
||||
import org.whispersystems.signalservice.api.services.DonationsService
|
||||
import org.whispersystems.signalservice.api.services.ProfileService
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceApi
|
||||
@@ -290,4 +291,8 @@ class MockApplicationDependencyProvider : AppDependencies.Provider {
|
||||
override fun provideProfileApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket, pushServiceSocket: PushServiceSocket): ProfileApi {
|
||||
return mockk(relaxed = true)
|
||||
}
|
||||
|
||||
override fun provideRemoteConfigApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket): RemoteConfigApi {
|
||||
return mockk(relaxed = true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,13 +19,10 @@ import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV3;
|
||||
import org.whispersystems.signalservice.api.websocket.SignalWebSocket;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
|
||||
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
|
||||
import org.whispersystems.signalservice.internal.push.RemoteConfigResponse;
|
||||
import org.whispersystems.signalservice.internal.push.WhoAmIResponse;
|
||||
import org.whispersystems.signalservice.internal.util.StaticCredentialsProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -115,17 +112,6 @@ public class SignalServiceAccountManager {
|
||||
pushServiceSocket.requestPushChallenge(sessionId, gcmRegistrationId);
|
||||
}
|
||||
|
||||
public RemoteConfigResult getRemoteConfig() throws IOException {
|
||||
RemoteConfigResponse response = this.pushServiceSocket.getRemoteConfig();
|
||||
Map<String, Object> out = new HashMap<>();
|
||||
|
||||
for (RemoteConfigResponse.Config config : response.getConfig()) {
|
||||
out.put(config.getName(), config.getValue() != null ? config.getValue() : config.isEnabled());
|
||||
}
|
||||
|
||||
return new RemoteConfigResult(out, response.getServerEpochTime());
|
||||
}
|
||||
|
||||
public void checkNetworkConnection() throws IOException {
|
||||
this.pushServiceSocket.pingStorageService();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.remoteconfig
|
||||
|
||||
import org.whispersystems.signalservice.api.NetworkResult
|
||||
import org.whispersystems.signalservice.api.websocket.SignalWebSocket
|
||||
import org.whispersystems.signalservice.internal.get
|
||||
import org.whispersystems.signalservice.internal.util.JsonUtil
|
||||
import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage
|
||||
import org.whispersystems.signalservice.internal.websocket.WebsocketResponse
|
||||
|
||||
/**
|
||||
* Remote configuration is a list of namespaced keys that clients may use for consistent configuration or behavior.
|
||||
*
|
||||
* Configuration values change over time, and the list should be refreshed periodically.
|
||||
*/
|
||||
class RemoteConfigApi(val authWebSocket: SignalWebSocket.AuthenticatedWebSocket) {
|
||||
|
||||
/**
|
||||
* Get remote config data from the server.
|
||||
*
|
||||
* GET /v1/config
|
||||
* - 200: Success
|
||||
*/
|
||||
fun getRemoteConfig(): NetworkResult<RemoteConfigResult> {
|
||||
val request = WebSocketRequestMessage.get("/v1/config")
|
||||
return NetworkResult.fromWebSocketRequest(signalWebSocket = authWebSocket, request = request, webSocketResponseConverter = RemoteConfigResultWebSocketResponseConverter())
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom converter for [RemoteConfigResult] as it needs the value of the timestamp header to construct the
|
||||
* complete result, not just the JSON body.
|
||||
*/
|
||||
private class RemoteConfigResultWebSocketResponseConverter : NetworkResult.WebSocketResponseConverter<RemoteConfigResult> {
|
||||
override fun convert(response: WebsocketResponse): NetworkResult<RemoteConfigResult> {
|
||||
return if (response.status < 200 || response.status > 299) {
|
||||
response.toStatusCodeError()
|
||||
} else {
|
||||
val remoteConfigResponse = JsonUtil.fromJson(response.body, RemoteConfigResponse::class.java)
|
||||
val transformed = remoteConfigResponse.config.associate { it.name to (it.value ?: it.isEnabled) }
|
||||
|
||||
NetworkResult.Success(
|
||||
RemoteConfigResult(
|
||||
config = transformed,
|
||||
serverEpochTimeMilliseconds = response.getHeader(SignalWebSocket.SERVER_DELIVERED_TIMESTAMP_HEADER).toLongOrNull() ?: System.currentTimeMillis()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,24 @@
|
||||
package org.whispersystems.signalservice.internal.push;
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.remoteconfig;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class RemoteConfigResponse {
|
||||
@JsonProperty
|
||||
private List<Config> config;
|
||||
|
||||
@JsonProperty
|
||||
private long serverEpochTime;
|
||||
|
||||
public List<Config> getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public long getServerEpochTime() {
|
||||
return serverEpochTime;
|
||||
}
|
||||
|
||||
public static class Config {
|
||||
@JsonProperty
|
||||
private String name;
|
||||
@@ -37,7 +37,7 @@ public class RemoteConfigResponse {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
public @Nullable String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api
|
||||
package org.whispersystems.signalservice.api.remoteconfig
|
||||
|
||||
data class RemoteConfigResult(
|
||||
val config: Map<String, Any>,
|
||||
val serverEpochTimeSeconds: Long
|
||||
val serverEpochTimeMilliseconds: Long
|
||||
)
|
||||
@@ -718,11 +718,6 @@ public class PushServiceSocket {
|
||||
}
|
||||
}
|
||||
|
||||
public RemoteConfigResponse getRemoteConfig() throws IOException {
|
||||
String response = makeServiceRequest("/v1/config", "GET", null);
|
||||
return JsonUtil.fromJson(response, RemoteConfigResponse.class);
|
||||
}
|
||||
|
||||
public void cancelInFlightRequests() {
|
||||
synchronized (connections) {
|
||||
Log.w(TAG, "Canceling: " + connections.size());
|
||||
|
||||
Reference in New Issue
Block a user