Compare commits

...

4 Commits

Author SHA1 Message Date
RoyceDa
1e00105d87 Вывод title
Some checks are pending
Build rosetta-wss / build (push) Waiting to run
2026-03-30 19:17:32 +02:00
RoyceDa
855deaa48a Уведомления для андроид, откат data-only
All checks were successful
Build rosetta-wss / build (push) Successful in 1m49s
2026-03-30 19:12:00 +02:00
RoyceDa
c1b287986d Исправление зарезервированного слова from для FCM
Some checks failed
Build rosetta-wss / build (push) Has been cancelled
2026-03-30 18:06:56 +02:00
RoyceDa
0a38409de4 data-only пуши
All checks were successful
Build rosetta-wss / build (push) Successful in 1m48s
2026-03-29 17:51:12 +02:00
3 changed files with 112 additions and 73 deletions

View File

@@ -2,6 +2,7 @@ package im.rosetta.service.dispatch;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -9,14 +10,15 @@ 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.AndroidConfig;
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;
import im.rosetta.database.repository.UserRepository;
import im.rosetta.service.dispatch.runtime.PushType;
import im.rosetta.service.services.UserService;
/**
@@ -54,7 +56,54 @@ public class FirebaseDispatcher {
}
}
public void sendPushNotification(String publicKey, String title, String messageText, String senderPublicKey) {
private Message buildMessage(String token, HashMap<String, String> data) {
String type = data.get("type");
if(type == null){
throw new IllegalArgumentException("Push notification type is required in data");
}
ApnsConfig.Builder apnsConfig = ApnsConfig.builder();
AndroidConfig.Builder androidConfig = AndroidConfig.builder();
Message.Builder messageBuilder = Message.builder()
.setToken(token)
.putAllData(data);
switch(type) {
case PushType.READ:
/**
* Тихий тип уведомления для очистки отправленных уведомлений на устройстве,
* не должен отображаться пользователю, поэтому не задаем звук и ставим contentAvailable для iOS и high priority для Android
*/
apnsConfig.setAps(Aps.builder().setContentAvailable(true).setSound("default").build());
androidConfig.setPriority(AndroidConfig.Priority.HIGH);
messageBuilder.setApnsConfig(apnsConfig.build());
messageBuilder.setAndroidConfig(androidConfig.build());
break;
case PushType.PERSONAL_MESSAGE:
case PushType.GROUP_MESSAGE:
/**
* Уведомление о новом сообщении, должно отображаться пользователю, поэтому задаем звук и high priority для Android
*/
String body = type == PushType.PERSONAL_MESSAGE ? "New message" : "New group message";
apnsConfig.setAps(Aps.builder().setSound("default").setMutableContent(true).build());
androidConfig.setPriority(AndroidConfig.Priority.HIGH);
messageBuilder.setApnsConfig(apnsConfig.build());
messageBuilder.setNotification(Notification.builder().setTitle(
data.getOrDefault("title", "Rosetta")
).setBody(body).build());
messageBuilder.setAndroidConfig(androidConfig.build());
break;
case PushType.CALL:
/**
* Звонок для андроид используем high priority, чтобы уведомление доставлялось даже если устройство в режиме Doze,
* для iOS используем VoIP уведомление, которое доставляется даже если приложение убито
*/
androidConfig.setPriority(AndroidConfig.Priority.HIGH);
messageBuilder.setAndroidConfig(androidConfig.build());
break;
}
return messageBuilder.build();
}
public void sendPushNotification(String publicKey, HashMap<String, String> data) {
executor.submit(() -> {
try {
List<String> tokens = userService.getNotificationsTokens(publicKey);
@@ -64,25 +113,7 @@ public class FirebaseDispatcher {
for (String token : tokens) {
try {
Message message = Message.builder()
.setNotification(Notification.builder()
.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();
Message message = this.buildMessage(token, data);
FirebaseMessaging.getInstance().send(message);
} catch (Exception e) {
e.printStackTrace();
@@ -94,45 +125,20 @@ 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 список публичных ключей пользователей
* @param title заголовок уведомления
* @param messageText текст уведомления
* @param data данные уведомления
*/
public void sendPushNotification(List<String> publicKeys, String title, String messageText, String senderPublicKey) {
public void sendPushNotification(List<String> publicKeys, HashMap<String, String> data) {
executor.submit(() -> {
for (String publicKey : publicKeys) {
sendPushNotificationSync(publicKey, title, messageText, senderPublicKey);
sendPushNotificationSync(publicKey, data);
}
});
}
/**
* Отправляет push-уведомление нескольким пользователям (асинхронно)
* @param publicKeys список публичных ключей пользователей
* @param title заголовок уведомления
* @param messageText текст уведомления
*/
public void sendPushNotification(List<String> 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) {
private void sendPushNotificationSync(String publicKey, HashMap<String, String> data) {
try {
List<String> tokens = userService.getNotificationsTokens(publicKey);
if (tokens == null || tokens.isEmpty()) {
@@ -141,24 +147,7 @@ public class FirebaseDispatcher {
for (String token : tokens) {
try {
Message message = Message.builder()
.setNotification(Notification.builder()
.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 != null ? senderPublicKey : "")
.setToken(token)
.build();
Message message = this.buildMessage(token, data);
FirebaseMessaging.getInstance().send(message);
} catch (Exception e) {

View File

@@ -1,5 +1,6 @@
package im.rosetta.service.dispatch;
import java.util.HashMap;
import java.util.List;
import im.rosetta.client.ClientManager;
@@ -11,6 +12,7 @@ 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.dispatch.runtime.PushType;
import im.rosetta.service.services.BufferService;
import im.rosetta.service.services.UserService;
import io.orprotocol.ProtocolException;
@@ -98,14 +100,26 @@ public class MessageDispatcher {
}
if(packet instanceof Packet7Read){
/**
* Если это пакет прочтения, то не отправляем пуш уведомление, так как это может привести к спаму пушами при чтении сообщений
* Если это пакет прочтения, то отправляем тихий пуш, что диалог прочитан, отправляем тому, кто читает диалог, чтобы
* клиент мог очистить пуши для этого диалога
*/
this.firebaseDispatcher.sendPushNotification(fromPublicKey, new HashMap<>(){
{
put("type", PushType.READ);
put("dialog", toPublicKey);
}
});
return;
}
/**
* Отправляем PUSH уведомление
*/
this.firebaseDispatcher.sendPushNotification(groupMembersPublicKeys, "Rosetta", "New message in group", toPublicKey.replace("#group:", ""));
this.firebaseDispatcher.sendPushNotification(groupMembersPublicKeys, new HashMap<>(){
{
put("type", PushType.GROUP_MESSAGE);
put("dialog", toPublicKey.replace("#group:", ""));
}
});
}
/**
@@ -156,15 +170,28 @@ public class MessageDispatcher {
}
if(packet instanceof Packet7Read){
/**
* Если это пакет прочтения, то не отправляем пуш уведомление,
* так как это может привести к спаму пушами при чтении сообщений
* Если это пакет прочтения, то отправляем тихий пуш, что диалог прочитан, отправляем тому, кто читает диалог, чтобы
* клиент мог очистить пуши для этого диалога
*/
this.firebaseDispatcher.sendPushNotification(fromPublicKey, new HashMap<>(){
{
put("type", PushType.READ);
put("dialog", toPublicKey);
put("title", user.getTitle());
}
});
return;
}
/**
* Отправляем PUSH уведомление получателю
*/
this.firebaseDispatcher.sendPushNotification(toPublicKey, user.getTitle(), "New message", fromPublicKey);
this.firebaseDispatcher.sendPushNotification(toPublicKey, new HashMap<>(){
{
put("type", PushType.PERSONAL_MESSAGE);
put("dialog", fromPublicKey);
put("title", user.getTitle());
}
});
}
/**

View File

@@ -0,0 +1,23 @@
package im.rosetta.service.dispatch.runtime;
/**
* Типы PUSH уведомлений, которые отправляются клиентам при получении новых сообщений, звонков и т.д.
*/
public class PushType {
/**
* Новое личное сообщение
*/
public static final String PERSONAL_MESSAGE = "personal_message";
/**
* Новое групповое сообщение
*/
public static final String GROUP_MESSAGE = "group_message";
/**
* Входящий звонок
*/
public static final String CALL = "call";
/**
* Прочтение сообщения для очистки отправленных уведомлений
*/
public static final String READ = "read";
}