From 2e8508a32c8b368bc930d9be427508452abdad0c Mon Sep 17 00:00:00 2001 From: RoyceDa Date: Fri, 20 Mar 2026 19:41:02 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8?= =?UTF-8?q?=20ConcurrentModificationException=20=D0=BF=D1=80=D0=B8=20?= =?UTF-8?q?=D0=BC=D0=BD=D0=BE=D0=B6=D0=B5=D1=81=D1=82=D0=B2=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/build.yaml | 1 + .../java/im/rosetta/client/ClientManager.java | 10 +++--- .../io/orprotocol/index/ClientIndexer.java | 34 ++++++++----------- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 837a2e3..2cd3697 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -1,4 +1,5 @@ name: Build rosetta-wss +run-name: Build and Deploy WSS on: push: branches: diff --git a/src/main/java/im/rosetta/client/ClientManager.java b/src/main/java/im/rosetta/client/ClientManager.java index 0ec7650..2bc28fd 100644 --- a/src/main/java/im/rosetta/client/ClientManager.java +++ b/src/main/java/im/rosetta/client/ClientManager.java @@ -1,7 +1,7 @@ package im.rosetta.client; -import java.util.HashSet; import java.util.List; +import java.util.Set; import im.rosetta.client.tags.ECIAuthentificate; @@ -33,7 +33,7 @@ public class ClientManager { } public boolean isClientConnected(String publicKey) { - HashSet clients = this.clientIndexer.getClients(ECIAuthentificate.class, "publicKey", publicKey); + Set clients = this.clientIndexer.getClients(ECIAuthentificate.class, "publicKey", publicKey); if(clients == null){ /** * Нет клиентов с таким публичным ключом @@ -59,7 +59,7 @@ public class ClientManager { * @throws ProtocolException если произошла ошибка при отправке пакета клиенту */ public void sendPacketToAuthorizedPK(String publicKey, Packet packet) throws ProtocolException { - HashSet clients = this.clientIndexer.getClients(ECIAuthentificate.class, "publicKey", publicKey); + Set clients = this.clientIndexer.getClients(ECIAuthentificate.class, "publicKey", publicKey); if(clients == null){ /** * Нет клиентов с таким публичным ключом, значит отправлять некому @@ -91,7 +91,7 @@ public class ClientManager { */ public void retranslate(Client client, Packet packet) throws ProtocolException{ ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class); - HashSet clients = this.clientIndexer + Set clients = this.clientIndexer .getClients(ECIAuthentificate.class, "publicKey", eciAuthentificate.getPublicKey()); if(clients == null){ /** @@ -129,7 +129,7 @@ public class ClientManager { * @return список клиентов с таким публичным ключом, может быть пустым, если клиентов с таким публичным ключом нет */ public List getPKClients(String publicKey) { - HashSet clients = this.clientIndexer.getClients(ECIAuthentificate.class, "publicKey", publicKey); + Set clients = this.clientIndexer.getClients(ECIAuthentificate.class, "publicKey", publicKey); if(clients == null){ /** * Нет клиентов с таким публичным ключом diff --git a/src/main/java/io/orprotocol/index/ClientIndexer.java b/src/main/java/io/orprotocol/index/ClientIndexer.java index 2c29859..ff83804 100644 --- a/src/main/java/io/orprotocol/index/ClientIndexer.java +++ b/src/main/java/io/orprotocol/index/ClientIndexer.java @@ -1,7 +1,7 @@ package io.orprotocol.index; -import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import io.orprotocol.client.Client; @@ -19,14 +19,8 @@ public class ClientIndexer { * Ключ третьего уровня - значение поля. * Значение - клиент. * Структура: tagClass -> indexName -> indexValue -> Client - * - * - * В качестве клиента используем HashSet потому что он очень хорош для операции contains, - * в отличие от ArrayList, который при поиске перебирает элементы один за другим, - * HashSet использует хэш таблицу и находит нужный объект почти - * мгновенно, независимо от того, 10 там элементов или миллион. */ - private final Map, Map>>> indices + private final ConcurrentHashMap, ConcurrentHashMap>>> indices = new ConcurrentHashMap<>(); public void indexTag(Client client, Class tagClass, T tag) { @@ -53,7 +47,7 @@ public class ClientIndexer { * Если использовать putIfAbsent, то он вернет null если значение там уже есть, * что не подходит */ - Map>> tagIndices = this.indices.computeIfAbsent( + ConcurrentHashMap>> tagIndices = this.indices.computeIfAbsent( tagClass, k -> new ConcurrentHashMap<>()); @@ -76,7 +70,7 @@ public class ClientIndexer { /** * Инициализируем имя индекса, почему compute, а не put - написано выше */ - Map> index = tagIndices.computeIfAbsent( + Map> index = tagIndices.computeIfAbsent( indexName, k -> new ConcurrentHashMap<>() ); @@ -86,7 +80,7 @@ public class ClientIndexer { * с одинковыми индексами, хотя такого лучше избегать, желательно чтобы индексы * были уникальными, тогда обработка будет быстрее всего */ - HashSet clients = index.computeIfAbsent(indexValue, k -> new HashSet<>()); + Set clients = index.computeIfAbsent(indexValue, k -> ConcurrentHashMap.newKeySet()); /** * Добавляем клиента в инициализиованный индекс */ @@ -117,7 +111,7 @@ public class ClientIndexer { * @internal */ public void removeTagIndex(Client client, Class tagClass) { - Map>> tagIndices = indices.get(tagClass); + ConcurrentHashMap>> tagIndices = indices.get(tagClass); if (tagIndices == null) { /** * Индекса и так не было, удалять нечего @@ -128,12 +122,12 @@ public class ClientIndexer { /** * Удаляем все ключи indexName по tagClass если Client == client */ - for (Map> index : tagIndices.values()) { + for (ConcurrentHashMap> index : tagIndices.values()) { /** * contains всегда использует переопределенный equals, по этому * обьекты клиентов сравниваются нормально */ - for(HashSet clients : index.values()){ + for(Set clients : index.values()){ if(!clients.contains(client)){ continue; } @@ -147,9 +141,9 @@ public class ClientIndexer { * @internal */ public void removeClientFromIndex(Client client) { - for(Map>> tagIndices : this.indices.values()){ - for(Map> index : tagIndices.values()){ - for(HashSet clients : index.values()){ + for(Map>> tagIndices : this.indices.values()){ + for(Map> index : tagIndices.values()){ + for(Set clients : index.values()){ /** * Этот тройной цикл не такой страшный, так как мы всего лишь * проходим по всем тегам (их немного), дальше идем по всем значениям в тегах @@ -173,7 +167,7 @@ public class ClientIndexer { * @param indexValue значение в теге * @return список клиентов с заданными значениями */ - public HashSet getClients(Class tagClass, String indexName, Object indexValue) { + public Set getClients(Class tagClass, String indexName, Object indexValue) { if(indexName == null || indexValue == null){ return null; } @@ -181,12 +175,12 @@ public class ClientIndexer { /** * Получение по индексу простое, так как каждое из заданных значений и есть ключ */ - Map>> tagIndices = indices.get(tagClass); + ConcurrentHashMap>> tagIndices = indices.get(tagClass); if (tagIndices == null) { return null; } - Map> index = tagIndices.get(indexName); + ConcurrentHashMap> index = tagIndices.get(indexName); if (index == null) { return null; } -- 2.49.1 From 5ac8f919ce227131c20e191243b67d6e19d4ab8c Mon Sep 17 00:00:00 2001 From: RoyceDa Date: Fri, 20 Mar 2026 20:02:14 +0200 Subject: [PATCH 2/3] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20APNS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/dispatch/FirebaseDispatcher.java | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/main/java/im/rosetta/service/dispatch/FirebaseDispatcher.java b/src/main/java/im/rosetta/service/dispatch/FirebaseDispatcher.java index f0fa523..90dd72c 100644 --- a/src/main/java/im/rosetta/service/dispatch/FirebaseDispatcher.java +++ b/src/main/java/im/rosetta/service/dispatch/FirebaseDispatcher.java @@ -9,6 +9,9 @@ import java.util.concurrent.Executors; import com.google.auth.oauth2.GoogleCredentials; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; +import com.google.firebase.messaging.ApnsConfig; +import com.google.firebase.messaging.Aps; +import com.google.firebase.messaging.ApsAlert; import com.google.firebase.messaging.FirebaseMessaging; import com.google.firebase.messaging.Message; import com.google.firebase.messaging.Notification; @@ -51,13 +54,7 @@ public class FirebaseDispatcher { } } - /** - * Отправляет push-уведомление пользователю с данным публичным ключом (асинхронно) - * @param publicKey публичный ключ пользователя - * @param title заголовок уведомления - * @param messageText текст уведомления - */ - public void sendPushNotification(String publicKey, String title, String messageText) { + public void sendPushNotification(String publicKey, String title, String messageText, String senderPublicKey) { executor.submit(() -> { try { List tokens = userService.getNotificationsTokens(publicKey); @@ -72,6 +69,17 @@ public class FirebaseDispatcher { .setTitle(title) .setBody(messageText) .build()) + .setApnsConfig(ApnsConfig.builder() + .setAps(Aps.builder() + .setMutableContent(true) + .setSound("default") + .setAlert(ApsAlert.builder() + .setTitle(title) + .setBody(messageText) + .build()) + .build()) + .build()) + .putData("sender_public_key", senderPublicKey) .setToken(token) .build(); @@ -86,6 +94,16 @@ public class FirebaseDispatcher { }); } + /** + * Отправляет push-уведомление пользователю с данным публичным ключом (асинхронно) + * @param publicKey публичный ключ пользователя + * @param title заголовок уведомления + * @param messageText текст уведомления + */ + public void sendPushNotification(String publicKey, String title, String messageText) { + sendPushNotification(publicKey, title, messageText, null); + } + /** * Отправляет push-уведомление нескольким пользователям (асинхронно) * @param publicKeys список публичных ключей пользователей -- 2.49.1 From 5625acb4be5771fba1b590d66cd7f3d0f4ee0a94 Mon Sep 17 00:00:00 2001 From: RoyceDa Date: Fri, 20 Mar 2026 20:09:17 +0200 Subject: [PATCH 3/3] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B2=D1=8B=D0=B2=D0=BE=D0=B4=D0=B0=20=D0=B8=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D1=82=D0=B5=D0=BB=D1=8F=20=D0=B2=20=D1=83=D0=B2=D0=B5?= =?UTF-8?q?=D0=B4=D0=BE=D0=BC=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/dispatch/FirebaseDispatcher.java | 20 ++++++++++++++++--- .../service/dispatch/MessageDispatcher.java | 18 +++++++++++++---- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/main/java/im/rosetta/service/dispatch/FirebaseDispatcher.java b/src/main/java/im/rosetta/service/dispatch/FirebaseDispatcher.java index 90dd72c..b08fe04 100644 --- a/src/main/java/im/rosetta/service/dispatch/FirebaseDispatcher.java +++ b/src/main/java/im/rosetta/service/dispatch/FirebaseDispatcher.java @@ -110,15 +110,29 @@ public class FirebaseDispatcher { * @param title заголовок уведомления * @param messageText текст уведомления */ - public void sendPushNotification(List publicKeys, String title, String messageText) { + public void sendPushNotification(List publicKeys, String title, String messageText, String senderPublicKey) { executor.submit(() -> { for (String publicKey : publicKeys) { - sendPushNotificationSync(publicKey, title, messageText); + sendPushNotificationSync(publicKey, title, messageText, senderPublicKey); } }); } - private void sendPushNotificationSync(String publicKey, String title, String messageText) { + /** + * Отправляет push-уведомление нескольким пользователям (асинхронно) + * @param publicKeys список публичных ключей пользователей + * @param title заголовок уведомления + * @param messageText текст уведомления + */ + public void sendPushNotification(List publicKeys, String title, String messageText) { + executor.submit(() -> { + for (String publicKey : publicKeys) { + sendPushNotificationSync(publicKey, title, messageText, null); + } + }); + } + + private void sendPushNotificationSync(String publicKey, String title, String messageText, String senderPublicKey) { try { List tokens = userService.getNotificationsTokens(publicKey); if (tokens == null || tokens.isEmpty()) { diff --git a/src/main/java/im/rosetta/service/dispatch/MessageDispatcher.java b/src/main/java/im/rosetta/service/dispatch/MessageDispatcher.java index f646872..8ab4efc 100644 --- a/src/main/java/im/rosetta/service/dispatch/MessageDispatcher.java +++ b/src/main/java/im/rosetta/service/dispatch/MessageDispatcher.java @@ -4,13 +4,15 @@ import java.util.List; import im.rosetta.client.ClientManager; import im.rosetta.client.tags.ECIAuthentificate; +import im.rosetta.database.entity.User; import im.rosetta.database.repository.BufferRepository; import im.rosetta.database.repository.GroupRepository; +import im.rosetta.database.repository.UserRepository; import im.rosetta.packet.Packet11Typeing; import im.rosetta.packet.Packet7Read; import im.rosetta.packet.base.PacketBaseDialog; import im.rosetta.service.services.BufferService; - +import im.rosetta.service.services.UserService; import io.orprotocol.ProtocolException; import io.orprotocol.client.Client; import io.orprotocol.packet.PacketManager; @@ -29,6 +31,8 @@ public class MessageDispatcher { private final BufferRepository bufferRepository = new BufferRepository(); private final BufferService bufferService; private final FirebaseDispatcher firebaseDispatcher = new FirebaseDispatcher(); + private final UserRepository userRepository = new UserRepository(); + private final UserService userService = new UserService(userRepository); public MessageDispatcher(ClientManager clientManager, PacketManager packetManager) { this.clientManager = clientManager; @@ -101,7 +105,7 @@ public class MessageDispatcher { /** * Отправляем PUSH уведомление */ - this.firebaseDispatcher.sendPushNotification(groupMembersPublicKeys, "Rosetta", "New message in group"); + this.firebaseDispatcher.sendPushNotification(groupMembersPublicKeys, "Rosetta", "New message in group", toPublicKey.replace("#group:", "")); } /** @@ -115,7 +119,13 @@ public class MessageDispatcher { public void sendPeer(PacketBaseDialog packet, Client client, boolean bufferizationNeed) throws ProtocolException { String fromPublicKey = packet.getFromPublicKey(); String toPublicKey = packet.getToPublicKey(); - + User user = this.userService.fromClient(client); + if(user == null){ + /** + * Если пользователь не найден, то не отправляем сообщение, так как у нас нет информации о том, кто отправляет сообщение + */ + return; + } /** * Ретранслируем сообщение ВСЕМ авторизованным сессиям отправителя КРОМЕ текущей, * чтобы синхронизировать отправленные сообщения @@ -154,7 +164,7 @@ public class MessageDispatcher { /** * Отправляем PUSH уведомление получателю */ - this.firebaseDispatcher.sendPushNotification(toPublicKey, "Rosetta", "New message"); + this.firebaseDispatcher.sendPushNotification(toPublicKey, user.getTitle(), "New message"); } /** -- 2.49.1