]> nmode's Git Repositories - signal-cli/commitdiff
Update dependencies
authorAsamK <asamk@gmx.de>
Wed, 23 Oct 2024 19:57:13 +0000 (21:57 +0200)
committerAsamK <asamk@gmx.de>
Fri, 25 Oct 2024 15:20:07 +0000 (17:20 +0200)
CHANGELOG.md
graalvm-config-dir/reflect-config.json
lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java
lib/src/main/java/org/asamk/signal/manager/internal/RegistrationManagerImpl.java
lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java
lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java
lib/src/main/java/org/asamk/signal/manager/util/Utils.java
settings.gradle.kts
src/main/java/org/asamk/signal/BaseConfig.java

index 80ed008dfb14815ffe26db474de755cac32547ae..524ac160f7488c3450cb7c50a46cdc555256dace 100644 (file)
@@ -2,6 +2,8 @@
 
 ## [Unreleased]
 
+Requires libsignal-client version 0.58.2
+
 ## [0.13.7] - 2024-09-28
 
 Requires libsignal-client version 0.58.0
@@ -706,7 +708,8 @@ Requires libsignal-client version 0.52.2
 ### Added
 
 - New parameters for `updateGroup` command for group v2 features:
-  `--description`, `--remove-member`, `--admin`, `--remove-admin`, `--reset-link`, `--link`, `--set-permission-add-member`, `--set-permission-edit-details`, `--expiration`
+  `--description`, `--remove-member`, `--admin`, `--remove-admin`, `--reset-link`, `--link`,
+  `--set-permission-add-member`, `--set-permission-edit-details`, `--expiration`
 - New `--admin` parameter for `quitGroup` to set an admin before leaving the group
 - New `--delete` parameter for `quitGroup`, to delete the local group data
 - New 'sendTyping' command to send typing indicators
index 2ae5c4522916ec67a06afb3473e09b6781c695bc..62b89b78fbec947c80b9e0bf1f97fce97223cf0d 100644 (file)
 {
   "name":"[Ljava.sql.Statement;"
 },
+{
+  "name":"[Lorg.asamk.signal.json.JsonAttachment;"
+},
+{
+  "name":"[Lorg.asamk.signal.json.JsonMention;"
+},
+{
+  "name":"[Lorg.asamk.signal.json.JsonPreview;"
+},
+{
+  "name":"[Lorg.asamk.signal.json.JsonQuotedAttachment;"
+},
+{
+  "name":"[Lorg.asamk.signal.json.JsonTextStyle;"
+},
+{
+  "name":"[Lorg.asamk.signal.manager.storage.accounts.AccountsStorage$Account;"
+},
 {
   "name":"[Lorg.whispersystems.signalservice.api.groupsv2.TemporalCredential;"
 },
   "allDeclaredFields":true,
   "allDeclaredMethods":true
 },
+{
+  "name":"java.util.AbstractMap",
+  "allDeclaredFields":true,
+  "queryAllDeclaredMethods":true
+},
 {
   "name":"java.util.ArrayList",
   "allDeclaredMethods":true,
   "allDeclaredMethods":true,
   "allDeclaredConstructors":true
 },
+{
+  "name":"java.util.ImmutableCollections$AbstractImmutableCollection",
+  "allDeclaredFields":true,
+  "queryAllDeclaredMethods":true
+},
+{
+  "name":"java.util.ImmutableCollections$AbstractImmutableList",
+  "allDeclaredFields":true,
+  "queryAllDeclaredMethods":true
+},
+{
+  "name":"java.util.ImmutableCollections$AbstractImmutableMap",
+  "allDeclaredFields":true,
+  "queryAllDeclaredMethods":true
+},
+{
+  "name":"java.util.ImmutableCollections$ListN",
+  "allDeclaredFields":true,
+  "queryAllDeclaredMethods":true,
+  "queryAllDeclaredConstructors":true
+},
+{
+  "name":"java.util.ImmutableCollections$Map1",
+  "allDeclaredFields":true,
+  "queryAllDeclaredMethods":true,
+  "queryAllDeclaredConstructors":true
+},
+{
+  "name":"java.util.ImmutableCollections$MapN",
+  "allDeclaredFields":true,
+  "queryAllDeclaredMethods":true,
+  "queryAllDeclaredConstructors":true
+},
 {
   "name":"java.util.LinkedHashMap",
   "allDeclaredMethods":true,
   "methods":[{"name":"getUnicodeLocaleType","parameterTypes":["java.lang.String"] }]
 },
 {
-  "name":"java.util.Map"
+  "name":"java.util.Map",
+  "queryAllDeclaredMethods":true
 },
 {
   "name":"java.util.Optional",
   "name":"org.asamk.signal.json.JsonContact",
   "allDeclaredFields":true,
   "queryAllDeclaredMethods":true,
+  "queryAllDeclaredConstructors":true,
   "methods":[{"name":"color","parameterTypes":[] }, {"name":"familyName","parameterTypes":[] }, {"name":"givenName","parameterTypes":[] }, {"name":"internal","parameterTypes":[] }, {"name":"isBlocked","parameterTypes":[] }, {"name":"isHidden","parameterTypes":[] }, {"name":"messageExpirationTime","parameterTypes":[] }, {"name":"name","parameterTypes":[] }, {"name":"nickFamilyName","parameterTypes":[] }, {"name":"nickGivenName","parameterTypes":[] }, {"name":"nickName","parameterTypes":[] }, {"name":"note","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"profile","parameterTypes":[] }, {"name":"profileSharing","parameterTypes":[] }, {"name":"unregistered","parameterTypes":[] }, {"name":"username","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] }]
 },
 {
   "name":"org.asamk.signal.json.JsonContact$JsonInternal",
   "allDeclaredFields":true,
   "queryAllDeclaredMethods":true,
+  "queryAllDeclaredConstructors":true,
   "methods":[{"name":"capabilities","parameterTypes":[] }, {"name":"discoverableByPhonenumber","parameterTypes":[] }, {"name":"sharesPhoneNumber","parameterTypes":[] }, {"name":"unidentifiedAccessMode","parameterTypes":[] }]
 },
 {
   "name":"org.asamk.signal.json.JsonContact$JsonProfile",
   "allDeclaredFields":true,
   "queryAllDeclaredMethods":true,
+  "queryAllDeclaredConstructors":true,
   "methods":[{"name":"about","parameterTypes":[] }, {"name":"aboutEmoji","parameterTypes":[] }, {"name":"familyName","parameterTypes":[] }, {"name":"givenName","parameterTypes":[] }, {"name":"hasAvatar","parameterTypes":[] }, {"name":"lastUpdateTimestamp","parameterTypes":[] }, {"name":"mobileCoinAddress","parameterTypes":[] }]
 },
 {
   "name":"org.signal.storageservice.protos.groups.local.DecryptedTimer",
   "fields":[{"name":"duration_"}]
 },
+{
+  "name":"org.slf4j.Logger"
+},
 {
   "name":"org.sqlite.JDBC"
 },
   "allDeclaredFields":true,
   "allDeclaredClasses":true,
   "queryAllDeclaredMethods":true,
+  "queryAllDeclaredConstructors":true,
   "methods":[{"name":"accountAttributes","parameterTypes":[] }, {"name":"aciPqLastResortPreKey","parameterTypes":[] }, {"name":"aciSignedPreKey","parameterTypes":[] }, {"name":"pniPqLastResortPreKey","parameterTypes":[] }, {"name":"pniSignedPreKey","parameterTypes":[] }, {"name":"verificationCode","parameterTypes":[] }]
 },
 {
index 5163c8fc27813e3415cdc34a81f17b6a0fcc1a86..ba18c63c2ac3df0cf0bfa829e748d06e6306bf9a 100644 (file)
@@ -168,12 +168,13 @@ public class AccountHelper {
             String newNumber, boolean voiceVerification, String captcha
     ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethodNotAvailableException {
         final var accountManager = dependencies.createUnauthenticatedAccountManager(newNumber, account.getPassword());
-        String sessionId = NumberVerificationUtils.handleVerificationSession(accountManager,
+        final var registrationApi = accountManager.getRegistrationApi();
+        String sessionId = NumberVerificationUtils.handleVerificationSession(registrationApi,
                 account.getSessionId(newNumber),
                 id -> account.setSessionId(newNumber, id),
                 voiceVerification,
                 captcha);
-        NumberVerificationUtils.requestVerificationCode(accountManager, sessionId, voiceVerification);
+        NumberVerificationUtils.requestVerificationCode(registrationApi, sessionId, voiceVerification);
     }
 
     public void finishChangeNumber(
@@ -280,13 +281,13 @@ public class AccountHelper {
                 pin,
                 context.getPinHelper(),
                 (sessionId1, verificationCode1, registrationLock) -> {
-                    final var accountManager = dependencies.getAccountManager();
+                    final var registrationApi = dependencies.getRegistrationApi();
                     try {
-                        Utils.handleResponseException(accountManager.verifyAccount(verificationCode1, sessionId1));
+                        Utils.handleResponseException(registrationApi.verifyAccount(verificationCode1, sessionId1));
                     } catch (AlreadyVerifiedException e) {
                         // Already verified so can continue changing number
                     }
-                    return Utils.handleResponseException(accountManager.changeNumber(new ChangePhoneNumberRequest(
+                    return Utils.handleResponseException(registrationApi.changeNumber(new ChangePhoneNumberRequest(
                             sessionId1,
                             null,
                             newNumber,
index 015d17a00eb190be64aa0f4e506a1c5d3558a0bc..eaaba787c5ca69f03365a3659f404ccde7f4a554 100644 (file)
@@ -129,14 +129,13 @@ public class RegistrationManagerImpl implements RegistrationManager {
                 return;
             }
 
-            String sessionId = NumberVerificationUtils.handleVerificationSession(unauthenticatedAccountManager,
+            final var registrationApi = unauthenticatedAccountManager.getRegistrationApi();
+            String sessionId = NumberVerificationUtils.handleVerificationSession(registrationApi,
                     account.getSessionId(account.getNumber()),
                     id -> account.setSessionId(account.getNumber(), id),
                     voiceVerification,
                     captcha);
-            NumberVerificationUtils.requestVerificationCode(unauthenticatedAccountManager,
-                    sessionId,
-                    voiceVerification);
+            NumberVerificationUtils.requestVerificationCode(registrationApi, sessionId, voiceVerification);
             account.setRegistered(false);
         } catch (DeprecatedVersionException e) {
             logger.debug("Signal-Server returned deprecated version exception", e);
@@ -196,7 +195,8 @@ public class RegistrationManagerImpl implements RegistrationManager {
 
             final var aciPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.ACI));
             final var pniPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.PNI));
-            final var response = Utils.handleResponseException(unauthenticatedAccountManager.registerAccount(null,
+            final var registrationApi = unauthenticatedAccountManager.getRegistrationApi();
+            final var response = Utils.handleResponseException(registrationApi.registerAccount(null,
                     recoveryPassword,
                     account.getAccountAttributes(null),
                     aciPreKeys,
@@ -256,12 +256,13 @@ public class RegistrationManagerImpl implements RegistrationManager {
             final PreKeyCollection aciPreKeys,
             final PreKeyCollection pniPreKeys
     ) throws IOException {
+        final var registrationApi = unauthenticatedAccountManager.getRegistrationApi();
         try {
-            Utils.handleResponseException(unauthenticatedAccountManager.verifyAccount(verificationCode, sessionId));
+            Utils.handleResponseException(registrationApi.verifyAccount(verificationCode, sessionId));
         } catch (AlreadyVerifiedException e) {
             // Already verified so can continue registering
         }
-        return Utils.handleResponseException(unauthenticatedAccountManager.registerAccount(sessionId,
+        return Utils.handleResponseException(registrationApi.registerAccount(sessionId,
                 null,
                 account.getAccountAttributes(registrationLock),
                 aciPreKeys,
index f4f72aca6a5a751f8dd5944cffc483b2ecd6d265..7ee2b39e94e4eabee7bb6ff9f818b8671b1e2442 100644 (file)
@@ -17,6 +17,7 @@ import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
 import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
 import org.whispersystems.signalservice.api.push.ServiceIdType;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.registration.RegistrationApi;
 import org.whispersystems.signalservice.api.services.ProfileService;
 import org.whispersystems.signalservice.api.svr.SecureValueRecovery;
 import org.whispersystems.signalservice.api.util.CredentialsProvider;
@@ -47,6 +48,7 @@ public class SignalDependencies {
 
     private SignalServiceAccountManager accountManager;
     private GroupsV2Api groupsV2Api;
+    private RegistrationApi registrationApi;
     private GroupsV2Operations groupsV2Operations;
     private ClientZkOperations clientZkOperations;
 
@@ -80,8 +82,14 @@ public class SignalDependencies {
         if (this.pushServiceSocket != null) {
             this.pushServiceSocket.close();
             this.pushServiceSocket = null;
+            this.accountManager = null;
+            this.messageReceiver = null;
+            this.messageSender = null;
+            this.profileService = null;
+            this.groupsV2Api = null;
+            this.registrationApi = null;
+            this.secureValueRecovery = null;
         }
-        this.messageSender = null;
         getSignalWebSocket().forceNewWebSockets();
     }
 
@@ -143,6 +151,10 @@ public class SignalDependencies {
         return getOrCreate(() -> groupsV2Api, () -> groupsV2Api = getAccountManager().getGroupsV2Api());
     }
 
+    public RegistrationApi getRegistrationApi() {
+        return getOrCreate(() -> registrationApi, () -> registrationApi = getAccountManager().getRegistrationApi());
+    }
+
     public GroupsV2Operations getGroupsV2Operations() {
         return getOrCreate(() -> groupsV2Operations,
                 () -> groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.signalServiceConfiguration()),
index f96e79d37ef409ded6605f857a6695eb22b747d4..1c5aa41178dca9c678f5e12f867d4a5b5fd84905 100644 (file)
@@ -10,14 +10,15 @@ import org.asamk.signal.manager.api.VerificationMethodNotAvailableException;
 import org.asamk.signal.manager.helper.PinHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.whispersystems.signalservice.api.SignalServiceAccountManager;
+import org.whispersystems.signalservice.api.NetworkResult;
 import org.whispersystems.signalservice.api.kbs.MasterKey;
 import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException;
 import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
 import org.whispersystems.signalservice.api.push.exceptions.PushChallengeRequiredException;
 import org.whispersystems.signalservice.api.push.exceptions.TokenNotAcceptedException;
-import org.whispersystems.signalservice.internal.ServiceResponse;
+import org.whispersystems.signalservice.api.registration.RegistrationApi;
 import org.whispersystems.signalservice.internal.push.LockedException;
+import org.whispersystems.signalservice.internal.push.PushServiceSocket.VerificationCodeTransport;
 import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse;
 import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
 
@@ -30,7 +31,7 @@ public class NumberVerificationUtils {
     private static final Logger logger = LoggerFactory.getLogger(NumberVerificationUtils.class);
 
     public static String handleVerificationSession(
-            SignalServiceAccountManager accountManager,
+            RegistrationApi registrationApi,
             String sessionId,
             Consumer<String> sessionIdSaver,
             boolean voiceVerification,
@@ -38,11 +39,11 @@ public class NumberVerificationUtils {
     ) throws CaptchaRequiredException, IOException, RateLimitException, VerificationMethodNotAvailableException {
         RegistrationSessionMetadataResponse sessionResponse;
         try {
-            sessionResponse = getValidSession(accountManager, sessionId);
+            sessionResponse = getValidSession(registrationApi, sessionId);
         } catch (PushChallengeRequiredException |
                  org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException e) {
             if (captcha != null) {
-                sessionResponse = submitCaptcha(accountManager, sessionId, captcha);
+                sessionResponse = submitCaptcha(registrationApi, sessionId, captcha);
             } else {
                 throw new CaptchaRequiredException("Captcha Required");
             }
@@ -77,7 +78,7 @@ public class NumberVerificationUtils {
 
         if (sessionResponse.getBody().getRequestedInformation().contains("captcha")) {
             if (captcha != null) {
-                sessionResponse = submitCaptcha(accountManager, sessionId, captcha);
+                sessionResponse = submitCaptcha(registrationApi, sessionId, captcha);
             }
             if (!sessionResponse.getBody().getAllowedToRequestCode()) {
                 throw new CaptchaRequiredException("Captcha Required");
@@ -88,14 +89,20 @@ public class NumberVerificationUtils {
     }
 
     public static void requestVerificationCode(
-            SignalServiceAccountManager accountManager, String sessionId, boolean voiceVerification
+            RegistrationApi registrationApi, String sessionId, boolean voiceVerification
     ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException {
-        final ServiceResponse<RegistrationSessionMetadataResponse> response;
+        final NetworkResult<RegistrationSessionMetadataResponse> response;
         final var locale = Utils.getDefaultLocale(Locale.US);
         if (voiceVerification) {
-            response = accountManager.requestVoiceVerificationCode(sessionId, locale, false);
+            response = registrationApi.requestSmsVerificationCode(sessionId,
+                    locale,
+                    false,
+                    VerificationCodeTransport.VOICE);
         } else {
-            response = accountManager.requestSmsVerificationCode(sessionId, locale, false);
+            response = registrationApi.requestSmsVerificationCode(sessionId,
+                    locale,
+                    false,
+                    VerificationCodeTransport.SMS);
         }
         try {
             Utils.handleResponseException(response);
@@ -140,37 +147,37 @@ public class NumberVerificationUtils {
     }
 
     private static RegistrationSessionMetadataResponse validateSession(
-            final SignalServiceAccountManager accountManager, final String sessionId
+            final RegistrationApi registrationApi, final String sessionId
     ) throws IOException {
         if (sessionId == null || sessionId.isEmpty()) {
             throw new NoSuchSessionException();
         }
-        return Utils.handleResponseException(accountManager.getRegistrationSession(sessionId));
+        return Utils.handleResponseException(registrationApi.getRegistrationSessionStatus(sessionId));
     }
 
     private static RegistrationSessionMetadataResponse requestValidSession(
-            final SignalServiceAccountManager accountManager
+            final RegistrationApi registrationApi
     ) throws IOException {
-        return Utils.handleResponseException(accountManager.createRegistrationSession(null, "", ""));
+        return Utils.handleResponseException(registrationApi.createRegistrationSession(null, "", ""));
     }
 
     private static RegistrationSessionMetadataResponse getValidSession(
-            final SignalServiceAccountManager accountManager, final String sessionId
+            final RegistrationApi registrationApi, final String sessionId
     ) throws IOException {
         try {
-            return validateSession(accountManager, sessionId);
+            return validateSession(registrationApi, sessionId);
         } catch (NoSuchSessionException e) {
             logger.debug("No registration session, creating new one.");
-            return requestValidSession(accountManager);
+            return requestValidSession(registrationApi);
         }
     }
 
     private static RegistrationSessionMetadataResponse submitCaptcha(
-            SignalServiceAccountManager accountManager, String sessionId, String captcha
+            RegistrationApi registrationApi, String sessionId, String captcha
     ) throws IOException, CaptchaRequiredException {
         captcha = captcha == null ? null : captcha.replace("signalcaptcha://", "");
         try {
-            return Utils.handleResponseException(accountManager.submitCaptchaToken(sessionId, captcha));
+            return Utils.handleResponseException(registrationApi.submitCaptchaToken(sessionId, captcha));
         } catch (PushChallengeRequiredException |
                  org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException |
                  TokenNotAcceptedException _e) {
index e6d8e4d3097b1df93da5c55b81a9bdb70404cd13..16aa32755ff343dc4d2a47c3b01ef8843d6f9974 100644 (file)
@@ -6,9 +6,9 @@ import org.signal.libsignal.protocol.fingerprint.Fingerprint;
 import org.signal.libsignal.protocol.fingerprint.NumericFingerprintGenerator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.whispersystems.signalservice.api.NetworkResult;
 import org.whispersystems.signalservice.api.push.ServiceId;
 import org.whispersystems.signalservice.api.util.StreamDetails;
-import org.whispersystems.signalservice.internal.ServiceResponse;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
@@ -140,15 +140,15 @@ public class Utils {
         return map;
     }
 
-    public static <T> T handleResponseException(final ServiceResponse<T> response) throws IOException {
-        final var throwableOptional = response.getExecutionError().or(response::getApplicationError);
-        if (throwableOptional.isPresent()) {
-            if (throwableOptional.get() instanceof IOException) {
-                throw (IOException) throwableOptional.get();
+    public static <T> T handleResponseException(final NetworkResult<T> response) throws IOException {
+        final var throwableOptional = response.getCause();
+        if (throwableOptional != null) {
+            if (throwableOptional instanceof IOException ioException) {
+                throw ioException;
             } else {
-                throw new IOException(throwableOptional.get());
+                throw new IOException(throwableOptional);
             }
         }
-        return response.getResult().orElse(null);
+        return response.successOrThrow();
     }
 }
index d06a1e1dc1d947ceeee5c5c3cf5319c465e8634c..ef9ec48658ff2a2e1cfcbf7309c4fb694294a5aa 100644 (file)
@@ -7,19 +7,19 @@ dependencyResolutionManagement {
     versionCatalogs {
         create("libs") {
             library("bouncycastle", "org.bouncycastle", "bcprov-jdk18on").version("1.78.1")
-            library("jackson.databind", "com.fasterxml.jackson.core", "jackson-databind").version("2.17.2")
+            library("jackson.databind", "com.fasterxml.jackson.core", "jackson-databind").version("2.18.0")
             library("argparse4j", "net.sourceforge.argparse4j", "argparse4j").version("0.9.0")
             library("dbusjava", "com.github.hypfvieh", "dbus-java-transport-native-unixsocket").version("5.0.0")
             version("slf4j", "2.0.16")
             library("slf4j.api", "org.slf4j", "slf4j-api").versionRef("slf4j")
             library("slf4j.jul", "org.slf4j", "jul-to-slf4j").versionRef("slf4j")
-            library("logback", "ch.qos.logback", "logback-classic").version("1.5.8")
+            library("logback", "ch.qos.logback", "logback-classic").version("1.5.11")
 
-            library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_109")
-            library("sqlite", "org.xerial", "sqlite-jdbc").version("3.46.1.0")
-            library("hikari", "com.zaxxer", "HikariCP").version("5.1.0")
-            library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").version("5.11.0")
-            library("junit.launcher", "org.junit.platform", "junit-platform-launcher").version("1.11.0")
+            library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_110")
+            library("sqlite", "org.xerial", "sqlite-jdbc").version("3.47.0.0")
+            library("hikari", "com.zaxxer", "HikariCP").version("6.0.0")
+            library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").version("5.11.3")
+            library("junit.launcher", "org.junit.platform", "junit-platform-launcher").version("1.11.3")
         }
     }
 }
index ae85882c0524e2a774d272d9c224ac6fcb08ee4c..7b182351db10e3fb3406a338c393719e01341a3f 100644 (file)
@@ -8,7 +8,7 @@ public class BaseConfig {
     public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion();
 
     static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT"))
-            .orElse("Signal-Android/7.18.2");
+            .orElse("Signal-Android/7.21.4");
     static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null
             ? "signal-cli"
             : PROJECT_NAME + "/" + PROJECT_VERSION;