Files
rosetta-wss/src/main/java/im/rosetta/service/dispatch/FirebaseDispatcher.java
RoyceDa 855deaa48a
All checks were successful
Build rosetta-wss / build (push) Successful in 1m49s
Уведомления для андроид, откат data-only
2026-03-30 19:12:00 +02:00

168 lines
7.2 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
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.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;
/**
* Класс для отправки push-уведомлений пользователям через Firebase Cloud Messaging (FCM).
*/
public class FirebaseDispatcher {
private UserRepository userRepository = new UserRepository();
private UserService userService = new UserService(userRepository);
private final ExecutorService executor = Executors.newFixedThreadPool(10);
public FirebaseDispatcher() {
initializeFirebase();
}
/**
* Инициализация Firebase Admin SDK
*/
private void initializeFirebase() {
if (FirebaseApp.getApps().isEmpty()) {
try {
String firebaseCredentialsPath = System.getenv("FIREBASE_CREDENTIALS_PATH");
if (firebaseCredentialsPath == null || firebaseCredentialsPath.isEmpty()) {
throw new IllegalStateException("FIREBASE_CREDENTIALS_PATH environment variable is not set");
}
FileInputStream serviceAccount = new FileInputStream(firebaseCredentialsPath);
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.build();
FirebaseApp.initializeApp(options);
} catch (IOException e) {
throw new RuntimeException("Failed to initialize Firebase", e);
}
}
}
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);
if (tokens == null || tokens.isEmpty()) {
return;
}
for (String token : tokens) {
try {
Message message = this.buildMessage(token, data);
FirebaseMessaging.getInstance().send(message);
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
/**
* Отправляет push-уведомление нескольким пользователям (асинхронно)
* @param publicKeys список публичных ключей пользователей
* @param data данные уведомления
*/
public void sendPushNotification(List<String> publicKeys, HashMap<String, String> data) {
executor.submit(() -> {
for (String publicKey : publicKeys) {
sendPushNotificationSync(publicKey, data);
}
});
}
private void sendPushNotificationSync(String publicKey, HashMap<String, String> data) {
try {
List<String> tokens = userService.getNotificationsTokens(publicKey);
if (tokens == null || tokens.isEmpty()) {
return;
}
for (String token : tokens) {
try {
Message message = this.buildMessage(token, data);
FirebaseMessaging.getInstance().send(message);
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (Exception e) {
// Логирование ошибки
}
}
/**
* Завершить работу executor при остановке приложения
*/
public void shutdown() {
executor.shutdown();
}
}