mirror of
https://github.com/AsamK/signal-cli.git
synced 2026-10-30 09:30:20 +00:00
Address numberless linking review cleanup
Reuse AccountApiV2 through SignalDependencies and use the shared suspend response helper while preserving linking error handling. Remove the unused account-number accessor and unsupported D-Bus version fallback. Keep numberless linking documentation in the man page and point the README to the wiki. Validate linking errors through HTTP response fixtures. All 196 tests pass on JDK 25 with an isolated D-Bus session; build and JSON schema generation also pass. Account-index classification is unchanged pending review discussion.
This commit is contained in:
parent
721312189a
commit
431a6bf286
@ -65,7 +65,7 @@ For a numbered account, ACCOUNT is your phone number in international format and
|
||||
should start with a "+" sign. (See [Wikipedia](https://en.wikipedia.org/wiki/List_of_country_calling_codes) for a list
|
||||
of all country codes.)
|
||||
For a linked account without a phone number, use its ACI (Account ID) instead.
|
||||
See [numberless accounts](doc/numberless-accounts.md) for details.
|
||||
See the [wiki](https://github.com/AsamK/signal-cli/wiki) for further documentation.
|
||||
|
||||
* Link to an existing account
|
||||
|
||||
|
||||
@ -1,73 +0,0 @@
|
||||
# Linking an account without a phone number
|
||||
|
||||
signal-cli can link as a secondary device to an account registered without a phone
|
||||
number, such as those created in Signal Android 8.28. Creating or recovering a
|
||||
primary numberless account using an Account Key is not supported.
|
||||
|
||||
Run `signal-cli link -n signal-cli`, then scan the QR code from the mobile app's
|
||||
Linked devices screen. The linking URI advertises `capabilities=nopni`. If linking
|
||||
completes, the CLI prints the account's ACI instead of a phone number.
|
||||
|
||||
Use that ACI with `-a` for subsequent commands:
|
||||
|
||||
```sh
|
||||
signal-cli -a YOUR_ACCOUNT_ACI receive
|
||||
signal-cli -a YOUR_ACCOUNT_ACI listGroups
|
||||
signal-cli -a YOUR_ACCOUNT_ACI send --note-to-self -m 'Numberless account test'
|
||||
```
|
||||
|
||||
When only one local account exists, `-a` can be omitted. Numbered accounts continue
|
||||
to accept their phone number or ACI. A numberless account cannot infer a country
|
||||
code for a recipient's phone number; use a complete international number beginning
|
||||
with `+`, an ACI, or a Signal username.
|
||||
|
||||
In JSON-RPC, use the ACI in the `account` parameter. `listAccounts` and `finishLink`
|
||||
return an `aci` field alongside `number`; `number` is null for a numberless account.
|
||||
Receive events use the ACI in their `account` field when there is no phone number.
|
||||
REST wrappers must understand those nullable numbers and ACI selectors before they
|
||||
can be assumed compatible.
|
||||
|
||||
Numberless accounts are also supported through D-Bus. Start
|
||||
`signal-cli daemon --dbus`, then use the ACI with the D-Bus client:
|
||||
|
||||
```sh
|
||||
signal-cli --dbus -a YOUR_ACCOUNT_ACI listGroups
|
||||
signal-cli --dbus -a YOUR_ACCOUNT_ACI send --note-to-self -m 'Numberless account test'
|
||||
```
|
||||
|
||||
In multi-account mode, D-Bus exports numberless accounts under
|
||||
`/org/asamk/Signal/ACI`, with the ACI's hyphens replaced by underscores. Numbered
|
||||
accounts retain their phone-number-based paths. Use `SignalControl.listAccounts`
|
||||
or `SignalControl.getAccount` to discover paths; `getAccount` accepts either a
|
||||
phone number or an ACI. `Signal.getSelfNumber` returns an empty string when the
|
||||
account has no phone number, and `Signal.getSelfACI` returns the ACI.
|
||||
`Signal.listRecipientIdentifiers` includes contacts without a phone number;
|
||||
`Signal.listNumbers` continues to return only phone numbers.
|
||||
|
||||
## Validation
|
||||
|
||||
Run `./gradlew test installDist` with JDK 25. Regression tests cover the linking
|
||||
request (including ACI authentication and omission of PNI keys), numbered-account
|
||||
linking, missing group credential salts, persisted account reload, account discovery
|
||||
(including superseded accounts and legacy indexes), group membership lookup,
|
||||
recipient-number formatting, and JSON account identifiers.
|
||||
|
||||
D-Bus transport tests require a session bus. To run them on an isolated bus, use
|
||||
`dbus-run-session -- ./gradlew --no-daemon :test --tests '*NumberlessDbusTest*' --rerun-tasks`.
|
||||
These tests exercise the D-Bus server and client with a local test backend, including
|
||||
mixed accounts, direct and group messages, self-directed operations, and account
|
||||
removal. They do not contact Signal. Without a session bus, only the D-Bus unit
|
||||
tests run and the transport tests are skipped.
|
||||
|
||||
Live acceptance requires a real mobile account and is separate from these tests:
|
||||
|
||||
1. Link a newly created secondary device using the numberless mobile account.
|
||||
2. Restart signal-cli and verify that `listAccounts` still reports the ACI.
|
||||
3. Send and receive a Note to Self message and a DM with a second account.
|
||||
4. Receive a group message, list the group, and send a reply to that test group.
|
||||
5. Run the JSON-RPC daemon, restart it, and repeat receiving and sending by ACI.
|
||||
6. Repeat with `daemon --dbus` and the `--dbus -a YOUR_ACCOUNT_ACI` client, checking
|
||||
direct messages, group messages, and Note to Self.
|
||||
|
||||
Only one signal-cli process may open an account's state at a time. Stop the daemon
|
||||
before running one-off commands against the same configuration directory.
|
||||
@ -8,8 +8,6 @@ import java.util.function.Consumer;
|
||||
|
||||
public interface MultiAccountManager extends AutoCloseable {
|
||||
|
||||
List<String> getAccountNumbers();
|
||||
|
||||
List<Manager> getManagers();
|
||||
|
||||
void addOnManagerAddedHandler(Consumer<Manager> handler);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package org.asamk.signal.manager.helper;
|
||||
|
||||
import org.asamk.signal.manager.api.BadRequestException;
|
||||
import org.asamk.signal.manager.api.CaptchaRequiredException;
|
||||
import org.asamk.signal.manager.api.DeviceLinkUrl;
|
||||
import org.asamk.signal.manager.api.IncorrectPinException;
|
||||
@ -18,7 +19,6 @@ import org.signal.core.models.ServiceId.ACI;
|
||||
import org.signal.core.models.ServiceId.PNI;
|
||||
import org.signal.core.util.Base64;
|
||||
import org.signal.core.util.crypto.DeviceNameCipher;
|
||||
import org.signal.libsignal.net.RequestResult;
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.NoSessionException;
|
||||
@ -28,7 +28,6 @@ import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
|
||||
import org.signal.libsignal.protocol.util.KeyHelper;
|
||||
import org.signal.libsignal.usernames.BaseUsernameException;
|
||||
import org.signal.libsignal.usernames.Username;
|
||||
import org.signal.network.api.AccountApiV2;
|
||||
import org.signal.network.api.AccountApiV2.SetAccountAttributesError;
|
||||
import org.signal.network.exceptions.NonSuccessfulResponseCodeException;
|
||||
import org.slf4j.Logger;
|
||||
@ -60,13 +59,11 @@ import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import kotlin.Unit;
|
||||
import okio.ByteString;
|
||||
|
||||
import static org.asamk.signal.manager.config.ServiceConfig.PREKEY_MAXIMUM_ID;
|
||||
import static org.asamk.signal.manager.util.Utils.handleResponseException;
|
||||
import static org.asamk.signal.manager.util.Utils.handleResponseExceptionSuspend;
|
||||
import static org.asamk.signal.manager.util.Utils.runSuspendBlocking;
|
||||
import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
|
||||
|
||||
public class AccountHelper {
|
||||
@ -575,17 +572,15 @@ public class AccountHelper {
|
||||
.setAccountAttributes(account.getAccountAttributes(null)));
|
||||
return;
|
||||
}
|
||||
final var api = new AccountApiV2(dependencies.getAuthenticatedSignalWebSocket());
|
||||
final RequestResult<Unit, ? extends SetAccountAttributesError> result = runSuspendBlocking(cont -> api.setAccountAttributes(
|
||||
account.getAccountAttributesV2(),
|
||||
cont));
|
||||
if (result instanceof RequestResult.NonSuccess<?> failure) {
|
||||
if (failure.getError() instanceof SetAccountAttributesError.Unauthorized) {
|
||||
try {
|
||||
handleResponseExceptionSuspend(cont -> dependencies.getAccountApiV2()
|
||||
.setAccountAttributes(account.getAccountAttributesV2(), cont));
|
||||
} catch (BadRequestException e) {
|
||||
if (e.getError() instanceof SetAccountAttributesError.Unauthorized) {
|
||||
throw new AuthorizationFailedException(401, "Authorization failed!");
|
||||
}
|
||||
throw new IOException("Account attribute update rate limited; try again later");
|
||||
}
|
||||
handleResponseException(result);
|
||||
}
|
||||
|
||||
public void addDevice(DeviceLinkUrl deviceLinkInfo) throws IOException, org.asamk.signal.manager.api.DeviceLimitExceededException {
|
||||
|
||||
@ -39,13 +39,6 @@ public class MultiAccountManagerImpl implements MultiAccountManager {
|
||||
managers.forEach(m -> m.addClosedListener(() -> this.removeManager(m)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAccountNumbers() {
|
||||
synchronized (managers) {
|
||||
return managers.stream().map(Manager::getSelfNumber).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Manager> getManagers() {
|
||||
synchronized (managers) {
|
||||
|
||||
@ -19,6 +19,7 @@ package org.asamk.signal.manager.internal;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.ProvisioningManager;
|
||||
import org.asamk.signal.manager.Settings;
|
||||
import org.asamk.signal.manager.api.BadRequestException;
|
||||
import org.asamk.signal.manager.api.UserAlreadyExistsException;
|
||||
import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
|
||||
import org.asamk.signal.manager.storage.SignalAccount;
|
||||
@ -29,7 +30,6 @@ import org.signal.core.models.ServiceId.ACI;
|
||||
import org.signal.core.models.ServiceId.PNI;
|
||||
import org.signal.core.models.backup.MediaRootBackupKey;
|
||||
import org.signal.core.util.crypto.DeviceNameCipher;
|
||||
import org.signal.libsignal.net.RequestResult;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair;
|
||||
import org.signal.libsignal.protocol.ecc.ECPrivateKey;
|
||||
@ -69,8 +69,7 @@ import kotlinx.coroutines.BuildersKt;
|
||||
import kotlinx.coroutines.CoroutineScope;
|
||||
|
||||
import static org.asamk.signal.manager.util.KeyUtils.generatePreKeysForType;
|
||||
import static org.asamk.signal.manager.util.Utils.handleResponseException;
|
||||
import static org.asamk.signal.manager.util.Utils.runSuspendBlocking;
|
||||
import static org.asamk.signal.manager.util.Utils.handleResponseExceptionSuspend;
|
||||
|
||||
public class ProvisioningManagerImpl implements ProvisioningManager, Closeable {
|
||||
|
||||
@ -324,23 +323,19 @@ public class ProvisioningManagerImpl implements ProvisioningManager, Closeable {
|
||||
attrs.getPniRegistrationId(),
|
||||
attrs.getName(),
|
||||
attrs.getCapabilities());
|
||||
final RequestResult<LinkDeviceResponse, ? extends RegisterAsLinkedDeviceError> result = runSuspendBlocking(cont -> registrationApi.registerAsSecondaryDevice(
|
||||
account.getAci(),
|
||||
account.getPassword(),
|
||||
provisioningCode,
|
||||
deviceAttributes,
|
||||
toRegistrationPreKeys(aciPreKeys),
|
||||
toRegistrationPreKeys(pniPreKeys),
|
||||
null,
|
||||
cont));
|
||||
return getLinkedDeviceId(result);
|
||||
}
|
||||
|
||||
static int getLinkedDeviceId(
|
||||
final RequestResult<LinkDeviceResponse, ? extends RegisterAsLinkedDeviceError> result
|
||||
) throws IOException {
|
||||
if (result instanceof RequestResult.NonSuccess<?> failure) {
|
||||
throw switch (failure.getError()) {
|
||||
try {
|
||||
final LinkDeviceResponse result = handleResponseExceptionSuspend(cont -> registrationApi.registerAsSecondaryDevice(
|
||||
account.getAci(),
|
||||
account.getPassword(),
|
||||
provisioningCode,
|
||||
deviceAttributes,
|
||||
toRegistrationPreKeys(aciPreKeys),
|
||||
toRegistrationPreKeys(pniPreKeys),
|
||||
null,
|
||||
cont));
|
||||
return result.getDeviceId();
|
||||
} catch (BadRequestException e) {
|
||||
throw switch (e.getError()) {
|
||||
case RegisterAsLinkedDeviceError.IncorrectVerification ignored ->
|
||||
new AuthorizationFailedException(403, "Device verification failed");
|
||||
case RegisterAsLinkedDeviceError.MissingCapability ignored ->
|
||||
@ -354,7 +349,6 @@ public class ProvisioningManagerImpl implements ProvisioningManager, Closeable {
|
||||
default -> new IOException("Unexpected device linking response");
|
||||
};
|
||||
}
|
||||
return handleResponseException(result).getDeviceId();
|
||||
}
|
||||
|
||||
static RegistrationApiV2.PreKeyCollection toRegistrationPreKeys(final PreKeyCollection preKeys) {
|
||||
|
||||
@ -8,6 +8,7 @@ import org.signal.libsignal.metadata.certificate.CertificateValidator;
|
||||
import org.signal.libsignal.net.Network;
|
||||
import org.signal.libsignal.protocol.SignalProtocolAddress;
|
||||
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
|
||||
import org.signal.network.api.AccountApiV2;
|
||||
import org.signal.network.api.AttachmentApi;
|
||||
import org.signal.network.api.CallingApi;
|
||||
import org.signal.network.api.CdsApi;
|
||||
@ -73,6 +74,7 @@ public class SignalDependencies {
|
||||
|
||||
private SignalServiceAccountManager accountManager;
|
||||
private AccountApi accountApi;
|
||||
private AccountApiV2 accountApiV2;
|
||||
private RateLimitChallengeApi rateLimitChallengeApi;
|
||||
private CdsApi cdsApi;
|
||||
private UsernameApi usernameApi;
|
||||
@ -221,6 +223,11 @@ public class SignalDependencies {
|
||||
return getOrCreate(() -> accountApi, () -> accountApi = new AccountApi(getAuthenticatedSignalWebSocket()));
|
||||
}
|
||||
|
||||
public AccountApiV2 getAccountApiV2() {
|
||||
return getOrCreate(() -> accountApiV2,
|
||||
() -> accountApiV2 = new AccountApiV2(getAuthenticatedSignalWebSocket()));
|
||||
}
|
||||
|
||||
public RateLimitChallengeApi getRateLimitChallengeApi() {
|
||||
return getOrCreate(() -> rateLimitChallengeApi,
|
||||
() -> rateLimitChallengeApi = new RateLimitChallengeApi(getAuthenticatedSignalWebSocket()));
|
||||
|
||||
@ -10,11 +10,11 @@ import org.asamk.signal.manager.storage.SignalAccount;
|
||||
import org.asamk.signal.manager.util.KeyUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.signal.core.models.ServiceId.ACI;
|
||||
import org.signal.core.models.ServiceId.PNI;
|
||||
import org.signal.libsignal.net.RequestResult;
|
||||
import org.signal.network.api.RegistrationApiV2;
|
||||
import org.signal.network.api.RegistrationApiV2.RegisterAsLinkedDeviceError;
|
||||
import org.signal.network.rest.SignalRestClient;
|
||||
import org.whispersystems.signalservice.api.push.ServiceIdType;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
|
||||
@ -36,7 +36,7 @@ import okio.Buffer;
|
||||
import okio.ByteString;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
@ -72,15 +72,35 @@ class NumberlessProvisioningTest {
|
||||
|
||||
@Test
|
||||
void numberlessLinkRequestOmitsPniAndAuthenticatesWithAci() throws Exception {
|
||||
checkLinkRequest(null, null);
|
||||
checkLinkRequest(null, null, 200, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void numberedLinkRequestStillIncludesPniKeys() throws Exception {
|
||||
checkLinkRequest("+12025550123", PNI_ID);
|
||||
checkLinkRequest("+12025550123", PNI_ID, 200, null);
|
||||
}
|
||||
|
||||
private void checkLinkRequest(final String number, final PNI pni) throws Exception {
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"422, Signal rejected the device linking request",
|
||||
"403, [403] Device verification failed",
|
||||
"409, Linked device is missing a required account capability",
|
||||
"411, Account has reached its linked device limit",
|
||||
"429, Device linking rate limited; try again later"
|
||||
})
|
||||
void linkingErrorsAreReportedWithoutCrashingOrIncludingServerBody(
|
||||
final int statusCode,
|
||||
final String errorMessage
|
||||
) throws Exception {
|
||||
checkLinkRequest(null, null, statusCode, errorMessage);
|
||||
}
|
||||
|
||||
private void checkLinkRequest(
|
||||
final String number,
|
||||
final PNI pni,
|
||||
final int statusCode,
|
||||
final String errorMessage
|
||||
) throws Exception {
|
||||
final var request = new AtomicReference<Request>();
|
||||
final var body = new AtomicReference<JsonNode>();
|
||||
final var mapper = new ObjectMapper();
|
||||
@ -91,9 +111,10 @@ class NumberlessProvisioningTest {
|
||||
body.set(mapper.readTree(buffer.readUtf8()));
|
||||
return new Response.Builder().request(chain.request())
|
||||
.protocol(Protocol.HTTP_1_1)
|
||||
.code(200)
|
||||
.message("OK")
|
||||
.body(ResponseBody.create("{\"deviceId\":2}", MediaType.get("application/json")))
|
||||
.code(statusCode)
|
||||
.message("Test response")
|
||||
.body(ResponseBody.create(statusCode == 200 ? "{\"deviceId\":2}" : "secret payload",
|
||||
MediaType.get("application/json")))
|
||||
.build();
|
||||
}).build();
|
||||
final var config = ServiceConfig.getServiceEnvironmentConfig(ServiceEnvironment.STAGING, "signal-cli-test");
|
||||
@ -125,13 +146,21 @@ class NumberlessProvisioningTest {
|
||||
final var pniKeys = pni == null
|
||||
? null
|
||||
: KeyUtils.generatePreKeysForType(account.getAccountData(ServiceIdType.PNI));
|
||||
final var deviceId = ProvisioningManagerImpl.registerLinkedDevice(api,
|
||||
account,
|
||||
"test-code",
|
||||
aciKeys,
|
||||
pniKeys);
|
||||
|
||||
assertEquals(2, deviceId);
|
||||
if (statusCode == 200) {
|
||||
assertEquals(2,
|
||||
ProvisioningManagerImpl.registerLinkedDevice(api, account, "test-code", aciKeys, pniKeys));
|
||||
} else {
|
||||
final var error = assertThrows(IOException.class,
|
||||
() -> ProvisioningManagerImpl.registerLinkedDevice(api,
|
||||
account,
|
||||
"test-code",
|
||||
aciKeys,
|
||||
pniKeys));
|
||||
assertEquals(errorMessage, error.getMessage());
|
||||
if (statusCode == 403) {
|
||||
assertInstanceOf(AuthorizationFailedException.class, error);
|
||||
}
|
||||
}
|
||||
assertEquals("PUT", request.get().method());
|
||||
assertEquals("/v1/devices/link", request.get().url().encodedPath());
|
||||
assertEquals(Credentials.basic(ACI_ID.toString(), "test-password"), request.get().header("Authorization"));
|
||||
@ -150,16 +179,4 @@ class NumberlessProvisioningTest {
|
||||
client.connectionPool().evictAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void linkingErrorsAreReportedWithoutCrashingOrIncludingServerBody() {
|
||||
final var incorrectVerification = RegisterAsLinkedDeviceError.IncorrectVerification.INSTANCE;
|
||||
assertThrows(AuthorizationFailedException.class,
|
||||
() -> ProvisioningManagerImpl.getLinkedDeviceId(new RequestResult.NonSuccess<>(incorrectVerification)));
|
||||
final var invalidRequest = new RegisterAsLinkedDeviceError.InvalidRequest("secret payload");
|
||||
final var error = assertThrows(IOException.class,
|
||||
() -> ProvisioningManagerImpl.getLinkedDeviceId(new RequestResult.NonSuccess<>(invalidRequest)));
|
||||
assertFalse(error.getMessage().contains("secret payload"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,6 +251,10 @@ If you want to connect to another signal-cli instance, you can just use this URI
|
||||
If you want to link to an Android/iOS device, scan the QR code that signal-cli prints in the terminal with the Signal app.
|
||||
If signal-cli is not running in a terminal, only the URI is printed and you can create a QR code from it yourself (e.g. with qrencode).
|
||||
|
||||
Accounts without a phone number can also be linked.
|
||||
After linking, use the account's ACI (Account ID) with `-a`; recipient phone numbers must include the international country code.
|
||||
Creating or recovering a primary account without a phone number using an Account Key is not supported.
|
||||
|
||||
*-n* NAME, *--name* NAME::
|
||||
Optionally specify a name to describe this new device.
|
||||
By default "cli" will be used.
|
||||
|
||||
@ -56,7 +56,6 @@ import org.asamk.signal.manager.api.UsernameLinkUrl;
|
||||
import org.asamk.signal.manager.api.UsernameStatus;
|
||||
import org.freedesktop.dbus.DBusPath;
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnection;
|
||||
import org.freedesktop.dbus.errors.UnknownMethod;
|
||||
import org.freedesktop.dbus.exceptions.DBusException;
|
||||
import org.freedesktop.dbus.exceptions.DBusExecutionException;
|
||||
import org.freedesktop.dbus.interfaces.DBusInterface;
|
||||
@ -815,7 +814,8 @@ public class DbusManagerImpl implements Manager {
|
||||
final var identifiers = addresses.stream()
|
||||
.map(RecipientIdentifier.Single::getIdentifier)
|
||||
.collect(Collectors.toSet());
|
||||
return listRecipientIdentifiers().stream()
|
||||
return signal.listRecipientIdentifiers()
|
||||
.stream()
|
||||
.filter(n -> addresses.isEmpty() || identifiers.contains(n))
|
||||
.map(n -> {
|
||||
final var contactBlocked = signal.isContactBlocked(n);
|
||||
@ -1090,15 +1090,6 @@ public class DbusManagerImpl implements Manager {
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> listRecipientIdentifiers() {
|
||||
try {
|
||||
return signal.listRecipientIdentifiers();
|
||||
} catch (UnknownMethod e) {
|
||||
// Older daemons only expose recipients with a known phone number.
|
||||
return signal.listNumbers();
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends DBusInterface> T getRemoteObject(final DBusPath path, final Class<T> type) {
|
||||
try {
|
||||
return connection.getRemoteObject(busname, path.getPath(), type);
|
||||
|
||||
@ -43,11 +43,6 @@ public class DbusMultiAccountManagerImpl implements MultiAccountManager {
|
||||
this.busname = busname;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAccountNumbers() {
|
||||
return getManagers().stream().map(Manager::getSelfNumber).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Manager> getManagers() {
|
||||
return signalControl.listAccounts()
|
||||
|
||||
@ -24,7 +24,6 @@ import org.asamk.signal.manager.internal.MultiAccountManagerImpl;
|
||||
import org.freedesktop.dbus.DBusPath;
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnection;
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder;
|
||||
import org.freedesktop.dbus.errors.UnknownMethod;
|
||||
import org.freedesktop.dbus.exceptions.DBusExecutionException;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -94,25 +93,6 @@ class NumberlessDbusTest {
|
||||
: new RecipientIdentifier.Number(number));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readsContactsFromOlderDaemonsWithoutTheNewRecipientListingMethod() {
|
||||
final var account = new TestAccount(NUMBER, ACI);
|
||||
account.recipients = List.of(Recipient.newBuilder().withAddress(new RecipientAddress(NUMBER)).build());
|
||||
final var signal = new DbusSignalImpl(account.manager, null, DbusConfig.getObjectPath(), true) {
|
||||
@Override
|
||||
public List<String> listRecipientIdentifiers() {
|
||||
throw new UnknownMethod("Not implemented by this daemon");
|
||||
}
|
||||
};
|
||||
final var client = new DbusManagerImpl(signal, null, "org.asamk.Signal.Test");
|
||||
assertEquals(NUMBER,
|
||||
client.getRecipients(false, Optional.empty(), Set.of(), Optional.empty())
|
||||
.getFirst()
|
||||
.getAddress()
|
||||
.number()
|
||||
.orElseThrow());
|
||||
}
|
||||
|
||||
// Run with: dbus-run-session -- ./gradlew --no-daemon :test --tests '*NumberlessDbusTest*' --rerun-tasks
|
||||
@Nested
|
||||
@Timeout(20)
|
||||
@ -148,7 +128,7 @@ class NumberlessDbusTest {
|
||||
|
||||
final var client = new DbusMultiAccountManagerImpl(control, connection, busname);
|
||||
assertEquals(3, client.getManagers().size());
|
||||
assertEquals(2, client.getAccountNumbers().stream().filter(n -> n == null).count());
|
||||
assertEquals(2, client.getManagers().stream().filter(m -> m.getSelfNumber() == null).count());
|
||||
assertNull(client.getManager(ACI).getSelfNumber());
|
||||
assertEquals(NUMBER, client.getManager(OTHER_ACI).getSelfNumber());
|
||||
|
||||
|
||||
@ -72,11 +72,6 @@ class SubscribeCallEventsTest {
|
||||
this.managers = new ArrayList<>(managers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAccountNumbers() {
|
||||
return managers.stream().map(Manager::getSelfNumber).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Manager> getManagers() {
|
||||
return managers;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user