Siirry pääsisältöön

Push Notifications

Avaa ChatGPT:ssä
26.02
Java API

Push-notifikaatiot voivat saavuttaa käyttäjät jopa silloin, kun sovellus ei ole auki. Selain tilaa kerran, sovellus tallentaa tilauksen, ja palvelin käyttää sitä lähettääkseen ilmoituksia, kun tapahtuma occurs. Push hallitsee tilausten tekemistä ja peruuttamista selainympäristössä. Palvelimella PushSender lähettää PushMessage:in tallennettuun tilaukseen.

Asennus ja edellytykset​

Push-notifikaatiot tarjoavat erillinen moduuli. Lisää se sovellukseesi:

pom.xml
<dependency>
<groupId>com.webforj</groupId>
<artifactId>webforj-push</artifactId>
</dependency>

Push-notifikaatiot vaativat:

  • Servletin käyttöönoton, kuten Jetty, Spring Boot tai WAR-tiedosto.
  • Avaintparin, joka luodaan alla ja jota käyttöönotto käyttää allekirjoittaakseen ilmoituksia.
  • Suojatun alkuperän. Selaimet hylkäävät tilaukset, joita toimitetaan muulla kuin https:llä, paitsi localhost-osoitteesta kehityksen aikana.
Suojatut alkuperät

Lisätietoja suojatuista konteksteista ja siitä, miksi ne ovat tärkeitä, katso Suojatut kontekstit MDN -dokumentaatiossa.

Avainten luominen​

Push-palvelut hyväksyvät vain ilmoituksia, jotka on allekirjoitettu käyttöönoton toimesta, johon selain on tilannut. Suorita rakennusliitännäinen kerran jokaiselle käyttöönotolle luodaksesi sen avaintparin:

mvn webforj:push-keys

Komento tulostaa kolme konfiguraatioriviä. Liitä ne application.properties-tiedostoon ilman lainausmerkkejä tai kopioi ne sellaisina kuin ne on tulostettu webforj.conf-tiedostoon. Korvaa aihealue käyttöönoton yhteystiedolla. Sen on oltava mailto: tai https://-osoite, jota push-palvelut voivat käyttää ottamaan yhteyttä operaattoriin.

application.properties
webforj.push.public-key=...
webforj.push.private-key=...
webforj.push.subject=mailto:ops@example.com
OminaisuusSelitys
webforj.push.public-keyJulkinen puolisko avaintparista, jota käyttöönotto käyttää ilmoitusten allekirjoittamiseen
webforj.push.private-keyYksityinen puolisko avaintparista. Kuten kaikki muut salaisuudet, pidä se poissa lähdekoodinhallinnasta
webforj.push.subjectKäyttöönoton yhteystieto. Sen on oltava mailto: tai https://-osoite, jota push-palvelut voivat käyttää ottaakseen yhteyttä operaattoriin

Sovellus lukee nämä ominaisuudet käynnistyksessä. Jos konfiguraatio sisältää vain joitain niistä, käynnistys epäonnistuu ja ilmoittaa, mitkä ominaisuudet puuttuvat.

Avainten kierto

Jokainen selain tilaa yhden avaintparin. Jos avaimet muuttuvat, push-palvelu hylkää olemassa olevat tilaukset. Seuraava subscribe()-kutsu jokaisessa selaimessa korvataa sen tilauksen.

Kuinka se toimii​

Prosessissa on kolme vaihetta:

  1. Tilaa. Näkymästä, Push.getCurrent().subscribe() pyytää käyttäjän lupaa ja palauttaa PushSubscription:n, joka tunnistaa selaimen osoitteen.
  2. Tallenna. Sovellus tallentaa tilauksen sen tietojen kanssa ja liittää sen vastaavaan käyttäjään.
  3. Lähetä. Myöhemmin mistä tahansa säikeestä, PushSender.send(subscription, message) siirtää viestin selaimen valmistajan push-palveluun. Palvelu näyttää ilmoituksen riippumatta siitä, onko sovellus auki vai ei.
Push.getCurrent().subscribe().thenAccept(subscriptions::save);

sender.send(subscription,
PushMessage.create("Tilauksesi on lähetetty").setUrl("/orders/42").build());

Seuraavat osiot selittävät, mitä selain näyttää ja kuinka käsitellä epäonnistumisia jokaisessa vaiheessa.

Instanssi​

Hanki push-instanssi nykyisestä ympäristöstä:

import com.webforj.push.Push;

Push push = Push.getCurrent();

if (Push.isPresent()) {
// ...
}

Push.ifPresent(p -> {
// ...
});

Selaimen tilaaminen​

Kutsu subscribe() käyttäjän toimintaan vastaamiseksi, kuten napsauttamalla "Ota ilmoitukset käyttöön" -painiketta. Palatettu PendingResult valmistuu selaimen PushSubscription:lla. Jos selain ei voi tilata, se valmistuu poikkeuksellisesti WebforjPushException -poikkeuksella.

PendingResult<PushSubscription> request = Push.getCurrent().subscribe();
request.thenAccept(subscription -> {
subscriptions.save(subscription);
});
request.exceptionally(throwable -> {
WebforjPushException error = (WebforjPushException) throwable.getCause();
PushStatus status = error.getStatus();
String message = error.getMessage();

return null;
});

Jos selain on jo tilattu, subscribe()-kutsumisen palauttaa olemassa olevan tilauksen. Voit siis kutsua sitä turvallisesti jokaisella vierailulla.

Selaimen lupa

Ensimmäinen subscribe()-kutsu pyytää käyttäjältä lupaa. Selain näyttää tämän kehotteen, se ei ole osa sovelluksen UI:ta. Koska selaimet näyttävät kehotteen vain käyttäjän toimintaan vastauksena, kutsu subscribe() napsautustunnistimesta sen sijaan, että sen näkymän rakentajasta.

Jos käyttäjä estää kehotteen, sovellus ei voi pyytää uudelleen kyseisestä alkuperästä.

Tilausten tallentaminen​

Tilauksen osoittaa yhden selaimen osoitteen ja kuuluu palvelimelle. Tallenna se sovelluksen tietojen kanssa, käyttäen sen päätepistettä avaimena. Sisällytä kaikki tiedot, joita sovellus tarvitsee valitakseen sopivat selaimet myöhemmin, kuten liitetty käyttäjä. Jokainen tilaus sisältää kolme tekstiarvoa:

ArvoMerkitys
getEndpoint()Toimitus-URL, jonka selainvalmistajan push-palvelu on määrittänyt
getP256dh()Selaimen julkinen avain
getAuth()Selaimen todennussalaisuus

Käyttäjä, joka tilaa kahdesta selainympäristöstä, on kahdelle tilaukselle. Poista tilaus, kun sen selain peruuttaa tai kun lähetys ilmoittaa, että se on vanhentunut. Katso Epäonnistumisen tila.

Tilauksen palauttaminen​

getSubscription() palauttaa selaimen nykyisen tilauksen tai tyhjän tuloksen, jos sellaista ei ole. Käytä sitä synkronoimaan palvelimen kopio esimerkiksi sen jälkeen, kun sovelluksen tallennus on nollattu:

Push.getCurrent().getSubscription().thenAccept(existing -> {
existing.ifPresent(subscriptions::save);
});

PushPermission kautta, getPermission() raportoi, myönnettiinkö käyttäjälle lupa, estettiinkö se vai onko käyttäjä vielä vastannut ilmoituskehotteeseen. Käytä tätä tulosta piilottaaksesi "Ota ilmoitukset käyttöön" -painike, kun napsauttaminen ei vaikuttaisi.

Peruuttaminen​

unsubscribe() peruuttaa selaimen tilauksen. Se valmistuu poistetulla tilauksella, jotta sovellus voi poistaa sen tallennetun kopion, tai tyhjällä tuloksella, jos selaimella ei ollut tilausta.

Push.getCurrent().unsubscribe().thenAccept(removed -> {
removed.ifPresent(subscriptions::delete);
});

Ilmoitusten lähettäminen​

PushSender lähettää PushMessage:n tallennettuun tilaukseen. Se allekirjoittaa viestin käyttöönoton avaimilla ja siirtää sen selaimen valmistajan push-palveluun. Tämä palvelu herättää selaimen ja näyttää ilmoituksen. Koska operaatio ei koskaan blokkaa kutsuvaa säiettä, voit kutsua sen napsautustunnistimesta, ajoitetusta työstä tai pyyntöjen käsittelijästä.

Kun ominaisuudet on konfiguroitu, lähettäjä on saatavilla beanina, jonka voit sisällyttää näkymiin, palveluihin ja ajoitettuihin tehtäviin. Korvataaksesi sen, määritä oma PushSender-bean.

@Route("/orders")
public class OrdersView extends Composite<FlexLayout> {

public OrdersView(PushSender sender, PushSubscriptions subscriptions) {
// ...
}
}

Ilman Springiä, new PushSender() lukee avaimet sovelluksen konfiguraatiosta. Luo lähettäjä sovellus-threadissä, joko näkymässä tai App.run():ssa, ja käytä sitä sitten mistä tahansa säikeestä. Kaikki lähettäjät jakavat yhden yhteysaltaan push-palveluille, joten niiden luominen missä tahansa ei aiheuta kustannuksia.

Ilmoituksia, jotka on lähetettävä myöhemmin tai sen jälkeen, kun käyttäjä on poistunut, käytä ajastinta palvelimella, kuten Springin TaskScheduler. Älä käytä sivun ajastinta kuten Interval, koska se pysähtyy, kun välilehti suljetaan.

Viestin muodostaminen​

Luo viesti sen otsikoilla, ja määritä sitten kaikki muut vaihtoehdot rakentajassa:

PushMessage message = PushMessage.create("Tilauksesi on lähetetty")
.setBody("Tilauksesi #42 on matkalla")
.setIcon("icons://icon-192x192.png")
.setUrl("/orders/42")
.setActions(List.of(new PushAction("track", "Seuraa", "/orders/42/tracking")))
.build();

PendingResult<Void> sent = sender.send(subscription, message);
sent.thenAccept(v -> status.setText("Lähetetty"));
sent.exceptionally(throwable -> {
WebforjPushException error = (WebforjPushException) throwable;
status.setText(error.getStatus() + ": " + error.getMessage());

return null;
});

send() palauttaa heti. PendingResult valmistuu, kun push-palvelu hyväksyy viestin, tai valmistuu poikkeuksellisesti, jos palvelu ei hyväksy sitä. Jos send() kutsutaan sovellus-threadissä, kuten kuuntimen kautta, sen palautteet suoritetaan siinä säikeessä ja voivat päivittää komponentteja. Jos istunto, joka kutsui send()-toimintoa, päättyy ennen kuin vastaus saapuu, palautteet eivät suoriteta, mutta ilmoitus toimitetaan silti.

Lähetys odottaa enintään 30 sekuntia push-palvelua ennen kuin se epäonnistuu UNREACHABLE-tilassa. Käytä setTimeout(Duration) muuttaaksesi aikarajaa jokaiselle lähetykselle.

VaihtoehtoVaikutus
setBodyAsettaa tekstin, joka näkyy otsikon alapuolella
setIconAsettaa ilmoituksen yhteydessä näytettävän kuvan. Se hyväksyy absoluuttiset URL-osoitteet sekä icons:// ja ws:// protokollat. Katso Resurssit. Se ei hyväksy context:// protokollaa, koska push-palvelut rajoittavat viestin 4 kB:hen
setUrlAsettaa sivun, joka avautuu, kun käyttäjä napsauttaa ilmoitusta. Suhteelliset URL-osoitteet ratkaistaan sovelluksen juurta vasten. Jos URL-osoitetta ei aseteta, avautuu sovelluksen juuressa
setActionsAsettaa ilmoituksessa näytettävät painikkeet, joissa jokaiselle painikkeelle on erillinen URL-osoite. Katso Selaintuki
setTagAsettaa tunnisteen. Jos näytettävällä ilmoituksella on sama tunniste, uusi ilmoitus korvataan sille
setSilentNäyttää ilmoituksen ilman ääntä tai värinää
setTimeToLiveAsettaa kuinka kauan push-palvelu säilyttää viestin offline-laitteelle, enintään neljä viikkoa
setUrgencyKäyttää PushUrgency ilmoittamaan laitteelle, että se voi viivästyttää alhaisen kiireellisyyden viestejä ja säästää akkua
setTopicKorvata viesti, joka odottaa vielä push-palvelussa, kun molemmat viestit sisältävät saman aiheen. Aiheet voivat sisältää enintään 32 merkkiä, jotka ovat URL:ssä turvallisia

Kun välilehti jo näyttää sivua, ilmoituksen napsauttaminen keskittyy sovellukseen. Muussa tapauksessa sivu avataan uudessa välilehdessä. Napsauttaessasi ilmoituksen painiketta avataan sen URL samalla tavalla.

Yksi ilmoitus viestiä kohti

Jokainen viesti näyttää ilmoituksen. Koska selaimet eivät herätä sivua viestille, joka ei näytä mitään, push-palveluja ei voida käyttää hiljaisten tietopäivitysten lähettämiseen.

Epäonnistumisen tila​

Kun subscribe() tai send() epäonnistuu, sen PendingResult ilmoittaa WebforjPushException:sta. PushStatus tunnistaa syyn:

TilaMilloinMitä tehdä
PERMISSION_DENIEDKäyttäjä on estänyt ilmoitukset sovellukselleSelitä, mistä käyttäjä voi sallia ilmoitukset selaimen asetuksissa
UNSUPPORTEDPush ei ole tuettu selaimessa, sivu ei ole suojatussa kontekstissa tai sovellusta ei ole otettu käyttöön servletinäPiilota toiminto
NOT_CONFIGUREDAinakin yksi webforj.push.* -ominaisuus puuttuu tai on puutteellinenLuo avaimet ja konfiguroi kaikki kolme ominaisuutta
SUBSCRIPTION_EXPIREDPush-palvelu ei enää tunnista tilausta, koska käyttäjä peruutti tai asensi selaimen uudelleenPoista tallennettu tilaus
REJECTEDPush-palvelu hylkäsi viestin; getStatusCode() sisältää sen vastauksenVahvista avaimet ja viestin koko
UNREACHABLEPush-palvelu ei vastannut ennen aikarajan umpeutumistaYritä myöhemmin uudelleen
UNKNOWNTallennettu päätepiste ei ole voimassa oleva URL, tai tilausta tai viestiä ei voitu koodataVahvista tallennettu tilaus

Poista vanhentuneet tilaukset jokaisen lähetyksen aikana:

sender.send(subscription, message).exceptionally(throwable -> {
WebforjPushException error = (WebforjPushException) throwable;
if (error.getStatus() == PushStatus.SUBSCRIPTION_EXPIRED) {
subscriptions.delete(subscription);
}

return null;
});
Vanhentuminen tapahtuu yksi viesti myöhässä

Push-palvelut poistoilmoittavat tilausten laiskasti. Ne hyväksyvät edelleen ensimmäisen viestin sen jälkeen, kun käyttäjä on perunut, mutta se ei mene minnekään. Seuraava viesti ilmoittaa SUBSCRIPTION_EXPIRED. Hyväksytty lähetys tarkoittaa, että viesti saavutti push-palvelun, ei sitä, että käyttäjä näki sen.

Selaintuki​

Kaikki tärkeimmät työpöytä- ja mobiiliselaimet näyttävät push-ilmoituksia tilattaessa. Pidä nämä rajoitukset mielessä:

  • iPhoneilla ja iPadeilla push toimii vain web-sovelluksille, jotka on lisätty Koti-näyttöön iOS 16.4:ssä tai sitä uudemmassa versiossa. Safari-välilehdessä subscribe() raportoi UNSUPPORTED. Katso Asennettavat sovellukset vaaditulle sovellussuunnitelmalle.
  • Safari ei näytä ilmoituspainikkeita. Se näyttää viestejä toiminnoilla ilman painikkeitaan, mutta napsauttamalla ilmoitusta avataan edelleen viestin URL.
  • Android- ja iOS-web-näkymät eivät näytä ilmoituksia.

Lisätietoja selaimittain, katso MDN showNotification-yhteensopivuus-taulukko.

Täydellinen esimerkki​

Seuraava näkymä tilaa ja peruuttaa selaimen, tallentaa tilaukset muistiin ja lähettää viestin jokaiselle tallennetulle tilaukselle. Se voi lähettää heti tai odottaa kahdeksan sekuntia käyttämällä Springin TaskScheduler:ia, jolloin välilehti voi sulkeutua ennen ilmoituksen saapumista. Sovellusluokka käyttää @EnableScheduling -komentoa, jotta aikatauluttaja on saatavilla.

PushSubscriptions.java
package com.example;

import com.webforj.push.PushSubscription;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.stereotype.Service;

@Service
public class PushSubscriptions {

private final Map<String, PushSubscription> byEndpoint = new ConcurrentHashMap<>();

public void save(PushSubscription subscription) {
byEndpoint.put(subscription.getEndpoint(), subscription);
}

public void delete(PushSubscription subscription) {
byEndpoint.remove(subscription.getEndpoint());
}

public Collection<PushSubscription> findAll() {
return byEndpoint.values();
}
}
PushView.java
subscribe.onClick(ev -> Push.getCurrent().subscribe()
.thenAccept(subscription -> {
subscriptions.save(subscription);
status.setText("Tilattu");
})
.exceptionally(throwable -> {
WebforjPushException error = (WebforjPushException) throwable.getCause();
status.setText(error.getStatus() == PushStatus.PERMISSION_DENIED
? "Ilmoitukset on estetty tässä selaimessa"
: error.getMessage());

return null;
}));

unsubscribe.onClick(ev -> Push.getCurrent().unsubscribe().thenAccept(removed -> {
removed.ifPresent(subscriptions::delete);
status.setText(removed.isPresent() ? "Peruutettu" : "Ei ollut tilausta");
}));

sendNow.onClick(ev -> sendToAll(subscriptions, sender, message.getValue(), status::setText));

sendLater.onClick(ev -> {
String text = message.getValue();
status.setText("Lähetetään 8 sekunnin kuluttua, sulje välilehti nyt");
scheduler.schedule(() -> sendToAll(subscriptions, sender, text, outcome -> {
}), Instant.now().plusSeconds(8));
});

Push.getCurrent().getSubscription().thenAccept(existing -> {
existing.ifPresent(subscriptions::save);
status.setText(existing.isPresent() ? "Tilattu" : "Ei tilattu");
});

self.add(status, message, subscribe, unsubscribe, sendNow, sendLater);