+ public static final int USERNAME_MIN_LENGTH = 3;
+ public static final int USERNAME_MAX_LENGTH = 32;
+
+ public String reserveUsername(String nickname) throws IOException, BaseUsernameException {
+ final var currentUsername = account.getUsername();
+ if (currentUsername != null) {
+ final var currentNickname = currentUsername.substring(0, currentUsername.indexOf('.'));
+ if (currentNickname.equals(nickname)) {
+ refreshCurrentUsername();
+ return currentUsername;
+ }
+ }
+
+ final var candidates = Username.generateCandidates(nickname, USERNAME_MIN_LENGTH, USERNAME_MAX_LENGTH);
+ final var candidateHashes = new ArrayList<String>();
+ for (final var candidate : candidates) {
+ candidateHashes.add(Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(candidate)));
+ }
+
+ final var response = dependencies.getAccountManager().reserveUsername(candidateHashes);
+ final var hashIndex = candidateHashes.indexOf(response.getUsernameHash());
+ if (hashIndex == -1) {
+ logger.warn("[reserveUsername] The response hash could not be found in our set of candidateHashes.");
+ throw new IOException("Unexpected username response");
+ }
+
+ logger.debug("[reserveUsername] Successfully reserved username.");
+ final var username = candidates.get(hashIndex);
+
+ dependencies.getAccountManager().confirmUsername(username, response);
+ account.setUsername(username);
+ account.getRecipientStore().resolveSelfRecipientTrusted(account.getSelfRecipientAddress());
+ logger.debug("[confirmUsername] Successfully confirmed username.");
+
+ return username;
+ }
+
+ public void refreshCurrentUsername() throws IOException, BaseUsernameException {
+ final var localUsername = account.getUsername();
+ if (localUsername == null) {
+ return;
+ }
+
+ final var whoAmIResponse = dependencies.getAccountManager().getWhoAmI();
+ final var serverUsernameHash = whoAmIResponse.getUsernameHash();
+ final var hasServerUsername = !isEmpty(serverUsernameHash);
+ final var localUsernameHash = Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(localUsername));
+
+ if (!hasServerUsername) {
+ logger.debug("No remote username is set.");
+ }
+
+ if (!Objects.equals(localUsernameHash, serverUsernameHash)) {
+ logger.debug("Local username hash does not match server username hash.");
+ }
+
+ if (!hasServerUsername || !Objects.equals(localUsernameHash, serverUsernameHash)) {
+ logger.debug("Attempting to resynchronize username.");
+ tryReserveConfirmUsername(localUsername, localUsernameHash);
+ } else {
+ logger.debug("Username already set, not refreshing.");
+ }
+ }
+
+ private void tryReserveConfirmUsername(final String username, String localUsernameHash) throws IOException {
+ final var response = dependencies.getAccountManager().reserveUsername(List.of(localUsernameHash));
+ logger.debug("[reserveUsername] Successfully reserved existing username.");
+ dependencies.getAccountManager().confirmUsername(username, response);
+ logger.debug("[confirmUsername] Successfully confirmed existing username.");
+ }
+
+ public void deleteUsername() throws IOException {
+ dependencies.getAccountManager().deleteUsername();
+ account.setUsername(null);
+ logger.debug("[deleteUsername] Successfully deleted the username.");
+ }
+