Предисловие
Приветствую Вас. Недавно передо мной стала задача — настроить Push-уведомления на сайте. С этим я столкнулся впервые и во много разобраться мне помогла эта статья. В ней же уже есть описание серверной стороны, но, в процессе изучения данной темы я обнаружил более удобный способ реализации средствами самой библиотеки Firebase. Собственно, о нем я и хотел бы вам рассказать, т.к. внятного объяснения в интернете мне не удалось найти.
Также данная статья может быть полезна программирующим на Node.js, Python и Go, поскольку библиотека есть и на этих языках.
Непосредственно к сути
В данной статье я расскажу только о серверной стороне.Итак:
(клиентскую часть Вы можете настроить используя ту самую статью)
- Для начала Вам нужно зайти на сайт, зарегистрировать и создать проект.
- Далее в левом верхнем углу нажимаем на шестерню и выбираем «Настройки проекта».
- Переходим на вкладку «Сервисные аккаунты», выбираем интересующий нас язык, нажимаем на «создание закрытого ключа» и скачиваем сгенерированный файл
Данный JSON-файл содержит в себе конфигурацию необходимую для библиотеки Firebase.Теперь займемся сервером
Для удобства объявим в application.properties путь к скаченному файлу
fcm.service-account-file = /path/to/file.json
Добавим необходимые зависимости в pom.xml
<dependency> <groupId>com.google.firebase</groupId> <artifactId>firebase-admin</artifactId> <version>6.7.0</version> </dependency>
Создадим бин возвращающий наш JSON:
@ConfigurationProperties(prefix = "fcm") @Component public class FcmSettings { private String serviceAccountFile; public String getServiceAccountFile() { return this.serviceAccountFile; } public void setServiceAccountFile(String serviceAccountFile) { this.serviceAccountFile = serviceAccountFile; } }
Конфиг-объект
@Getter @Setter public class PushNotifyConf { private String title; private String body; private String icon; private String click_action; private String ttlInSeconds; public PushNotifyConf() { } public PushNotifyConf(String title, String body, String icon, String click_action, String ttlInSeconds) { this.title = title; this.body = body; this.icon = icon; this.click_action = click_action; this.ttlInSeconds = ttlInSeconds; } }
Поля:
- title — Оглавление уведомления
- body — текст уведомления
- icon — ссылка на картинку
- click_action — ссылка, куда отправится пользователь при клике на уведомление (с названием, пример в сервисе)
Их можно добавлять несколько, но все отобразит не каждый браузер (ниже пример из Chroma)
- ttlInSeconds — время актуальности уведомления
И сервис, в котором и будет вся логика отправки уведомлений:
@Service public class FcmClient { public FcmClient(FcmSettings settings) { Path p = Paths.get(settings.getServiceAccountFile()); try (InputStream serviceAccount = Files.newInputStream(p)) { FirebaseOptions options = new FirebaseOptions.Builder() .setCredentials(GoogleCredentials.fromStream(serviceAccount)) .build(); FirebaseApp.initializeApp(options); } catch (IOException e) { Logger.getLogger(FcmClient.class.getName()) .log(Level.SEVERE, null, e); } } public String sendByTopic(PushNotifyConf conf, String topic) throws InterruptedException, ExecutionException { Message message = Message.builder().setTopic(topic) .setWebpushConfig(WebpushConfig.builder() .putHeader("ttl", conf.getTtlInSeconds()) .setNotification(createBuilder(conf).build()) .build()) .build(); String response = FirebaseMessaging.getInstance() .sendAsync(message) .get(); return response; } public String sendPersonal(PushNotifyConf conf, String clientToken) throws ExecutionException, InterruptedException { Message message = Message.builder().setToken(clientToken) .setWebpushConfig(WebpushConfig.builder() .putHeader("ttl", conf.getTtlInSeconds()) .setNotification(createBuilder(conf).build()) .build()) .build(); String response = FirebaseMessaging.getInstance() .sendAsync(message) .get(); return response; } public void subscribeUsers(String topic, List<String> clientTokens) throws FirebaseMessagingException { for (String token : clientTokens) { TopicManagementResponse response = FirebaseMessaging.getInstance() .subscribeToTopic(Collections.singletonList(token), topic); } } private WebpushNotification.Builder createBuilder(PushNotifyConf conf){ WebpushNotification.Builder builder = WebpushNotification.builder(); builder.addAction(new WebpushNotification .Action(conf.getClick_action(), "Открыть")) .setImage(conf.getIcon()) .setTitle(conf.getTitle()) .setBody(conf.getBody()); return builder; } }
Я: — Firebase, почему так много билдим?
Firebase: — Потому что
- Конструктор служит для инициализации FirebaseApp с использованием нашего JSON-файла
- Метод sendByTopic() производит отправку уведомлений пользователям подписанным на заданную тему.
- Метод subscribeUsers() подписывет на тему (topic) пользователей (clientTokens).
может выполняться асинхронно, для этого используется .subscribeToTopicAsync()
- Метод sendPersonal() реализует отправку персонального уведомления пользователю (clientToken)
- Метод createBuilder() создает сообщение
Результат

Другой браузер

Иконок нет потому, что Ubuntu:)
Подводим итоги
По сути, библиотека Firebase собирает нам JSON примерно такого вида:
{from: "Server key" notification: { title: "Привет Habr" actions: (1) [ 0: Object { action: "https://habr.com/ru/top/", title: "Открыть" } ] length: 1 body: "как-то так" image: "https://habrastorage.org/webt/7i/k5/77/7ik577fzskgywduy_2mfauq1gxs.png" } priority: "normal"}
А на стороне клиента Вы уже парсите его, как нравится.
Спасибо за внимание!
Полезные ссылки:
firebase.google.com/docs/reference/admin/java/reference/com/google/firebase/messaging/WebpushNotification
habr.com/ru/post/321924/#otpravka-uvedomleniy-s-servera
firebase.google.com/docs/web/setup
