Push service in java

Рассылка Push-уведомлений с SpringBoot сервера

Приветствую Вас. Недавно передо мной стала задача — настроить Push-уведомления на сайте. С этим я столкнулся впервые и во много разобраться мне помогла эта статья. В ней же уже есть описание серверной стороны, но, в процессе изучения данной темы я обнаружил более удобный способ реализации средствами самой библиотеки Firebase. Собственно, о нем я и хотел бы вам рассказать, т.к. внятного объяснения в интернете мне не удалось найти.

Также данная статья может быть полезна программирующим на Node.js, Python и Go, поскольку библиотека есть и на этих языках.

Непосредственно к сути

В данной статье я расскажу только о серверной стороне.
(клиентскую часть Вы можете настроить используя ту самую статью)

  • Для начала Вам нужно зайти на сайт, зарегистрировать и создать проект.
  • Далее в левом верхнем углу нажимаем на шестерню и выбираем «Настройки проекта».
  • Переходим на вкладку «Сервисные аккаунты», выбираем интересующий нас язык, нажимаем на «создание закрытого ключа» и скачиваем сгенерированный файл

Теперь займемся сервером

Для удобства объявим в application.properties путь к скаченному файлу

fcm.service-account-file = /path/to/file.json

Добавим необходимые зависимости в pom.xml

 com.google.firebase firebase-admin 6.7.0 

Создадим бин возвращающий наш 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 — ссылка, куда отправится пользователь при клике на уведомление (с названием, пример в сервисе)

И сервис, в котором и будет вся логика отправки уведомлений:

@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 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; >> 
  1. Конструктор служит для инициализации FirebaseApp с использованием нашего JSON-файла
  2. Метод sendByTopic() производит отправку уведомлений пользователям подписанным на заданную тему.
  3. Метод subscribeUsers() подписывет на тему (topic) пользователей (clientTokens).

image

image

Подводим итоги

По сути, библиотека Firebase собирает нам JSON примерно такого вида:

 ]​​ length: 1​​ body: "как-то так"​​ image: "https://habrastorage.org/webt/7i/k5/77/7ik577fzskgywduy_2mfauq1gxs.png"​​ > ​priority: "normal">

А на стороне клиента Вы уже парсите его, как нравится.

Источник

Как отправлять веб-push-уведомления на Java

Узнайте, как подписаться на веб-push-уведомления в браузере и отправлять push-сообщения с сервера Java. Помеченный java, веб-разработчик, весенняя загрузка, машинопись.

Веб-push-уведомления – это способ информировать пользователей вашего приложения о том, что произошло что-то важное.

Пользователи могут получать веб-push-уведомления, даже если они не активно используют ваше приложение, например, если приложение открыто на фоновой вкладке или даже если оно не открыто.

Push-уведомления широко поддерживаются всеми браузерами, кроме Safari: 78% веб-пользователей используют браузер, который их поддерживает.

В этом уроке я покажу вам как подписаться на уведомления в браузере и как отправлять уведомления с Java-сервера .

Видеоверсия

Немного предыстории: как работают веб-push-уведомления

Веб-push-уведомления основаны на двух веб-стандартах: API уведомлений и Push API (который, в свою очередь, использует Service Worker ). Для их работы требуется протокол HTTPS.

Подписка на push-уведомления

  • Сервер делится своим открытым ключом с браузером
  • Браузер использует открытый ключ для подписки на push-сервис (у каждого браузера свой собственный).
  • Служба push возвращает подписку с уникальным URL-адресом конечной точки, который можно использовать для отправки push-сообщений
  • Подписка сохраняется на сервере

Отправка push-уведомлений

  • Сервер подписывает заголовок авторизации своим закрытым ключом
  • Сервер отправляет сообщение на уникальный URL-адрес конечной точки
  • Push-сервер расшифровывает заголовок авторизации
  • Push-сервер отправляет сообщение на устройство/браузер

Настройте проект и сгенерируйте ДЕЙСТВИТЕЛЬНЫЕ ключи

Я использую Vaadin Fusion для этого примера. Использование термоядерного синтеза Пружинный ботинок на задней панели и Горит на передней панели.

Здесь я расскажу только о ключевых шагах. Вы можете найти полный исходный код на ГитХаб .

Вы можете создать новый проект Fusion с помощью командной строки Vaadin:

npx @vaadin/cli init --fusion push-app

Создайте набор ПУСТЫХ ключей с помощью пакета web-push npm.

npx web-push generate-vapid-keys

Создайте новый файл .environment в каталоге проекта и используйте его для хранения ключей. Добавьте его в свой файл .gitignore чтобы вы случайно не опубликовали его.

export VAPID_PUBLIC_KEY=BAwZxXp0K. export VAPID_PRIVATE_KEY=1HLNMKEE.

Добавьте зависимость библиотеки Java Web Push в pom.xml :

 nl.martijndwars web-push 5.1.1  

Загрузите файл среды и запустите приложение:

Создайте службу Java для обработки подписок и отправки уведомлений

Создайте новую службу загрузки Spring, MessageService.java . Эта служба будет считывать в ключах и

package com.example.application; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.Security; import java.time.LocalTime; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import javax.annotation.PostConstruct; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.jose4j.lang.JoseException; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import nl.martijndwars.webpush.Notification; import nl.martijndwars.webpush.PushService; import nl.martijndwars.webpush.Subscription; @Service public class MessageService < @Value("$") private String publicKey; @Value("$") private String privateKey; private PushService pushService; private List subscriptions = new ArrayList<>(); @PostConstruct private void init() throws GeneralSecurityException < Security.addProvider(new BouncyCastleProvider()); pushService = new PushService(publicKey, privateKey); >public String getPublicKey() < return publicKey; >public void subscribe(Subscription subscription) < System.out.println("Subscribed to " + subscription.endpoint); this.subscriptions.add(subscription); >public void unsubscribe(String endpoint) < System.out.println("Unsubscribing from " + endpoint); subscriptions = subscriptions.stream().filter(s ->!endpoint.equals(s.endpoint)).collect(Collectors.toList()); > public void sendNotification(Subscription subscription, String messageJson) < try < pushService.send(new Notification(subscription, messageJson)); >catch (GeneralSecurityException | IOException | JoseException | ExecutionException | InterruptedException e) < e.printStackTrace(); >> @Scheduled(fixedRate = 15000) private void sendNotifications() < System.out.println("Sending notifications to all subscribers"); var json = """ < "title": "Server says hello!", "body": "It is now: %s" >"""; subscriptions.forEach(subscription -> < sendNotification(subscription, String.format(json, LocalTime.now())); >); > > 

Некоторые ключевые моменты, на которые следует обратить внимание:

  • Аннотация @Value(«$») считывает переменные среды в поля.
  • Служба хранит подписки в Списке . В более практичном приложении вы бы сохранили их в базе данных вместе с пользователем.
  • Вы отправляете push-уведомления с помощью push-сервиса.отправить(новое уведомление(подписка, Json сообщения)) . Полезная нагрузка также может быть обычным текстом, но JSON является более гибким.
  • Сервис каждые 15 секунд рассылает всем абонентам уведомление, содержащее текущее время.

Создайте конечную точку для доступа к серверу

Далее вам нужен способ доступа к серверу из браузера. В Vaadin Fusion вы делаете это, определяя Конечную точку . Конечная точка будет генерировать типы машинописных текстов и методы доступа TS, которые вы можете использовать в клиентском коде.

package com.example.application; import com.vaadin.flow.server.connect.Endpoint; import com.vaadin.flow.server.connect.auth.AnonymousAllowed; import nl.martijndwars.webpush.Subscription; @Endpoint @AnonymousAllowed public class MessageEndpoint < private MessageService messageService; public MessageEndpoint(MessageService messageService) < this.messageService = messageService; >public String getPublicKey() < return messageService.getPublicKey(); >public void subscribe(Subscription subscription) < messageService.subscribe(subscription); >public void unsubscribe(String endpoint) < messageService.unsubscribe(endpoint); >>

Некоторые вещи, которые следует отметить:

  • Конечные точки защищены по умолчанию. Вы можете разрешить анонимный доступ с помощью @AnonymousAllowed .
  • Конечная точка внедряет службу сообщений и делегирует ей подписку и отказ от подписки.

Подписаться на уведомления в браузере

Создайте представление для подписки на уведомления. Компонент Освещенный элемент отслеживает две части состояния:

  • разрешил ли пользователь уведомления
  • есть ли у пользователя существующая push-подписка
import < customElement, html, state >from "lit-element"; import "@vaadin/vaadin-button"; import < View >from "../view"; import * as server from "Frontend/generated/MessageEndpoint"; @customElement("notifications-view") export class NotificationsView extends View < @state() denied = Notification.permission === "denied"; @state() subscribed = false; render() < return html` 

Web Push Notifications 📣

$ You have blocked notifications. You need to manually enable them in your browser. ` : ""> $Hooray! You are subscribed to receive notifications 🙌

Unsubscribe ` : html`

You are not yet subscribed to receive notifications.

Subscribe `> `; > async firstUpdated() < const registration = await navigator.serviceWorker.getRegistration(); this.subscribed = !!(await registration?.pushManager.getSubscription()); >async subscribe() < const notificationPermission = await Notification.requestPermission(); if (notificationPermission === "granted") < const publicKey = await server.getPublicKey(); const registration = await navigator.serviceWorker.getRegistration(); const subscription = await registration?.pushManager.subscribe(< userVisibleOnly: true, applicationServerKey: this.urlB64ToUint8Array(publicKey), >); if (subscription) < this.subscribed = true; // Serialize keys uint8array ->base64 server.subscribe(JSON.parse(JSON.stringify(subscription))); > > else < this.denied = true; >> async unsubscribe() < const registration = await navigator.serviceWorker.getRegistration(); const subscription = await registration?.pushManager.getSubscription(); if (subscription) < await subscription.unsubscribe(); await server.unsubscribe(subscription.endpoint); this.subscribed = false; >> private urlB64ToUint8Array(base64String: string) < const padding = "=".repeat((4 - (base64String.length % 4)) % 4); const base64 = (base64String + padding) .replace(/\-/g, "+") .replace(/_/g, "/"); const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) < outputArray[i] = rawData.charCodeAt(i); >return outputArray; > >

Важной частью здесь является метод subscribe() -. Вот что он делает:

  1. Запрашивает у пользователя разрешение на показ уведомлений с помощью Notification.requestPermission() . Ответ будет “удовлетворен” или “отклонен”. записка: Если пользователь откажется, вы не сможете спросить его снова. Обязательно запрашивайте пользователя только тогда, когда он ожидает и хочет получать уведомления.
  2. Если пользователь дает разрешение, извлеките открытый ключ с сервера и используйте pushManager Service Worker для подписки на уведомления. Ключ Сервера приложений – это массив UINT8, содержащий открытый ключ. Вам нужно преобразовать его с помощью прилагаемого метода. (Не самый удобный API 🤷 ♂ ️)
  3. Если подписка завершится успешно, отправьте ее на сервер.

Обрабатывать входящие push-сообщения в Сервисном работнике

Как только вы подпишетесь на уведомления, сервер будет отправлять уведомления каждые 15 секунд.

Переопределите сотрудника службы, созданного Vaadin, скопировав цель/sw.ts -> интерфейс/sw.ts .

Добавьте следующих двух слушателей в sw.ts :

self.addEventListener("push", (e) => < const data = e.data?.json(); if (data) < self.registration.showNotification(data.title, < body: data.body, >); > >); self.addEventListener("notificationclick", (e) => < e.notification.close(); e.waitUntil(focusOrOpenWindow()); >); async function focusOrOpenWindow() < const url = new URL("/", self.location.origin).href; const allWindows = await self.clients.matchAll(< type: "window", >); const appWindow = allWindows.find((w) => w.url === url); if (appWindow) < return appWindow.focus(); >else < return self.clients.openWindow(url); >>
Оцените статью