aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/traccar/notificators/NotificatorFirebase.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/traccar/notificators/NotificatorFirebase.java')
-rw-r--r--src/main/java/org/traccar/notificators/NotificatorFirebase.java169
1 files changed, 106 insertions, 63 deletions
diff --git a/src/main/java/org/traccar/notificators/NotificatorFirebase.java b/src/main/java/org/traccar/notificators/NotificatorFirebase.java
index f91ec25a0..be95fb28e 100644
--- a/src/main/java/org/traccar/notificators/NotificatorFirebase.java
+++ b/src/main/java/org/traccar/notificators/NotificatorFirebase.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2018 - 2023 Anton Tananaev (anton@traccar.org)
* Copyright 2018 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,88 +16,131 @@
*/
package org.traccar.notificators;
-import com.fasterxml.jackson.annotation.JsonProperty;
+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.AndroidNotification;
+import com.google.firebase.messaging.ApnsConfig;
+import com.google.firebase.messaging.Aps;
+import com.google.firebase.messaging.FirebaseMessaging;
+import com.google.firebase.messaging.FirebaseMessagingException;
+import com.google.firebase.messaging.MessagingErrorCode;
+import com.google.firebase.messaging.MulticastMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.traccar.Context;
+import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.model.Event;
+import org.traccar.model.Notification;
import org.traccar.model.Position;
import org.traccar.model.User;
-import org.traccar.notification.NotificationMessage;
+import org.traccar.notification.MessageException;
import org.traccar.notification.NotificationFormatter;
-
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.client.InvocationCallback;
-
-public class NotificatorFirebase extends Notificator {
+import org.traccar.session.cache.CacheManager;
+import org.traccar.storage.Storage;
+import org.traccar.storage.StorageException;
+import org.traccar.storage.query.Columns;
+import org.traccar.storage.query.Condition;
+import org.traccar.storage.query.Request;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+@Singleton
+public class NotificatorFirebase implements Notificator {
private static final Logger LOGGER = LoggerFactory.getLogger(NotificatorFirebase.class);
- private final String url;
- private final String key;
+ private final NotificationFormatter notificationFormatter;
+ private final Storage storage;
+ private final CacheManager cacheManager;
- public static class Notification {
- @JsonProperty("title")
- private String title;
- @JsonProperty("body")
- private String body;
- @JsonProperty("sound")
- private String sound;
- }
+ @Inject
+ public NotificatorFirebase(
+ Config config, NotificationFormatter notificationFormatter,
+ Storage storage, CacheManager cacheManager) throws IOException {
- public static class Message {
- @JsonProperty("registration_ids")
- private String[] tokens;
- @JsonProperty("notification")
- private Notification notification;
- }
+ this.notificationFormatter = notificationFormatter;
+ this.storage = storage;
+ this.cacheManager = cacheManager;
- public NotificatorFirebase() {
- this(
- "https://fcm.googleapis.com/fcm/send",
- Context.getConfig().getString(Keys.NOTIFICATOR_FIREBASE_KEY));
- }
+ InputStream serviceAccount = new ByteArrayInputStream(
+ config.getString(Keys.NOTIFICATOR_FIREBASE_SERVICE_ACCOUNT).getBytes());
- protected NotificatorFirebase(String url, String key) {
- this.url = url;
- this.key = key;
+ FirebaseOptions options = FirebaseOptions.builder()
+ .setCredentials(GoogleCredentials.fromStream(serviceAccount))
+ .build();
+
+ FirebaseApp.initializeApp(options);
}
@Override
- public void sendSync(long userId, Event event, Position position) {
- final User user = Context.getPermissionsManager().getUser(userId);
- if (user.getAttributes().containsKey("notificationTokens")) {
-
- NotificationMessage shortMessage = NotificationFormatter.formatMessage(userId, event, position, "short");
-
- Notification notification = new Notification();
- notification.title = shortMessage.getSubject();
- notification.body = shortMessage.getBody();
- notification.sound = "default";
-
- Message message = new Message();
- message.tokens = user.getString("notificationTokens").split("[, ]");
- message.notification = notification;
-
- Context.getClient().target(url).request()
- .header("Authorization", "key=" + key)
- .async().post(Entity.json(message), new InvocationCallback<Object>() {
- @Override
- public void completed(Object o) {
+ public void send(Notification notification, User user, Event event, Position position) throws MessageException {
+ if (user.hasAttribute("notificationTokens")) {
+
+ var shortMessage = notificationFormatter.formatMessage(user, event, position, "short");
+
+ List<String> registrationTokens = new ArrayList<>(
+ Arrays.asList(user.getString("notificationTokens").split("[, ]")));
+
+ MulticastMessage message = MulticastMessage.builder()
+ .setNotification(com.google.firebase.messaging.Notification.builder()
+ .setTitle(shortMessage.getSubject())
+ .setBody(shortMessage.getBody())
+ .build())
+ .setAndroidConfig(AndroidConfig.builder()
+ .setNotification(AndroidNotification.builder()
+ .setSound("default")
+ .build())
+ .build())
+ .setApnsConfig(ApnsConfig.builder()
+ .setAps(Aps.builder()
+ .setSound("default")
+ .build())
+ .build())
+ .addAllTokens(registrationTokens)
+ .putData("eventId", String.valueOf(event.getId()))
+ .build();
+
+ try {
+ var result = FirebaseMessaging.getInstance().sendMulticast(message);
+ List<String> failedTokens = new LinkedList<>();
+ var iterator = result.getResponses().listIterator();
+ while (iterator.hasNext()) {
+ int index = iterator.nextIndex();
+ var response = iterator.next();
+ if (!response.isSuccessful()) {
+ MessagingErrorCode error = response.getException().getMessagingErrorCode();
+ if (error == MessagingErrorCode.INVALID_ARGUMENT || error == MessagingErrorCode.UNREGISTERED) {
+ failedTokens.add(registrationTokens.get(index));
+ }
+ LOGGER.warn("Firebase user {} error", user.getId(), response.getException());
+ }
}
-
- @Override
- public void failed(Throwable throwable) {
- LOGGER.warn("Firebase notification error", throwable);
+ if (!failedTokens.isEmpty()) {
+ registrationTokens.removeAll(failedTokens);
+ if (registrationTokens.isEmpty()) {
+ user.getAttributes().remove("notificationTokens");
+ } else {
+ user.set("notificationTokens", String.join(",", registrationTokens));
+ }
+ storage.updateObject(user, new Request(
+ new Columns.Include("attributes"),
+ new Condition.Equals("id", user.getId())));
+ cacheManager.updateOrInvalidate(true, user);
}
- });
+ } catch (FirebaseMessagingException | StorageException e) {
+ LOGGER.warn("Firebase error", e);
+ }
}
}
- @Override
- public void sendAsync(long userId, Event event, Position position) {
- sendSync(userId, event, position);
- }
-
}