Siirry pääsisältöön

Push Notifications

Avaa ChatGPT:ssä
26.02
Java API

Push-ilmoitukset voivat saavuttaa käyttäjät jopa silloin, kun sovellus ei ole avoinna. Selain tilaa sen kerran, sovellus tallentaa tilauksen ja palvelin käyttää sitä ilmoitusten toimittamiseen tapahtumien esiintyessä. Push hallitsee tilausta ja peruutusta selaimessa. Palvelimella PushSender lähettää PushMessage:n tallennettuun tilaukseen.

Asennus ja vaatimus

Push-ilmoitukset tarjotaan erillisellä moduulilla. Lisää se sovellukseesi:

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

Push-ilmoitukset vaativat:

  • Servlet-levitys, kuten Jetty, Spring Boot tai WAR-tiedosto.
  • Avaipari, joka on luotu alla, jota levitys käyttää ilmoitusten allekirjoittamiseen.
  • Turvallinen alkuperä. Selaimet hylkäävät tilaukset, joita tarjotaan muilla kuin https-protokollilla, paitsi localhost-osoitteesta kehityksen aikana.
Turvalliset alkuperät

Lisätietoja turvallisista konteksteista ja niiden tärkeydestä saat Turvalliset kontekstit MDN dokumentista.

Avainten luominen

Push-palvelut hyväksyvät vain ilmoituksia, joita on allekirjoitettu levityksellä, johon selain on tilannut. Suorita rakennuslaajennus kerran jokaiselle levitykselle avainparin luomiseksi:

mvn webforj:push-keys

Komentorivi tulostaa kolme konfiguraatiorivie. Liitä ne application.properties-tiedostoon ilman lainausmerkkejä, tai kopioi ne sellaisenaan webforj.conf-tiedostoon. Korvaa aiheella levityksen yhteystiedot. Sen on oltava mailto: tai https://-osoite, jota push-palvelut voivat käyttää operaattorin tavoittamiseen.

application.properties
webforj.push.public-key=...
webforj.push.private-key=...
webforj.push.subject=mailto:ops@example.com
OminaisuusSelitys
webforj.push.public-keyAvaiparin julkinen osa, jota levitys käyttää ilmoitusten allekirjoittamiseen
webforj.push.private-keyAvaiparin yksityinen osa. Kuten muutkin salaisuudet, pidä se poissa lähdekoodin hallinnasta
webforj.push.subjectLevityksen yhteystiedot. Sen on oltava mailto: tai https://-osoite, jonka kautta push-palvelut voivat tavoittaa operaattorin

Sovellus lukee nämä ominaisuudet käynnistyksen yhteydessä. Jos konfiguraatio sisältää vain osan niistä, käynnistys epäonnistuu ja ilmoittaa puuttuvat ominaisuudet.

Avainten kiertäminen

Jokainen selain tilaavat yhden avainparin. Jos avaimet muuttuvat, push-palvelu hylkää olemassa olevat tilaukset. Seuraava subscribe()-kutsu jokaisessa selaimessa korvataan sen tilaus.

Miten 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 yhdistää sen vastaavaan käyttäjään.
  3. Lähetä. Myöhemmin, mistä tahansa säikeestä, PushSender.send(subscription, message) välittää viestin selaimen tarjoajalle push-palvelulle. Palvelu näyttää ilmoituksen riippumatta siitä, onko sovellus avoinna vai ei.
Push.getCurrent().subscribe().thenAccept(subscriptions::save);

sender.send(subscription,
PushMessage.create("Tilauksen toimitus").setUrl("/tilaukset/42").build());

Seuraavat osat 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() vastauksena käyttäjän toimintaan, kuten napsauttamalla "Ota ilmoitukset käyttöön" -painiketta. Palautettu PendingResult valmistuu selaimen PushSubscription:n kanssa. Jos selain ei voi tilata, se valmistuu poikkeuksellisesti WebforjPushException:n kanssa.

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()-kutsuminen palauttaa olemassa olevan tilauksen. Voit siten kutsua sitä turvallisesti jokaisella vierailulla.

Selaimen lupa

Ensimmäinen kutsu subscribe() 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 toiminnan seurauksena, kutsu subscribe() napsautustapahtumasta sen sijaan, että näkymäkonstruoitteesta.

Jos käyttäjä estää kehotteen, sovellus ei voi pyytää sitä uudelleen tälle alkuperälle.

Tilauksien tallentaminen

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

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

Käyttäjä, joka tilaa kahdesta selaimesta, saa kaksi tilausta. Poista tilaus, kun sen selain peruuttaa tai kun lähetys ilmoittaa, että se on vanhentunut. Katso Epäonnistumistila.

Tilauksen palauttaminen

getSubscription() palauttaa selaimen nykyisen tilauksen tai tyhjät tulokset, jos sellaista ei ole. Käytä sitä synkronoimaan palvelimen kopiot, esimerkiksi sen jälkeen, kun sovelluksen tallennus on palautettu:

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

Kautta PushPermission, getPermission() ilmoittaa, onko käyttäjä myöntänyt, evännyt tai ei ole vielä vastannut ilmoituskehotteeseen. Käytä tätä tulosta piilottaaksesi "Ota ilmoitukset käyttöön" -painikkeen, jos sen napsauttaminen ei vaikuta.

Peruuttaminen

unsubscribe() peruuttaa selaimen tilauksen. Se valmistuu poistetuilla tilauksilla, jotta sovellus voi poistaa sen tallennetun kopion tai tyhjillä tuloksilla, 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 levityksen avaimilla ja välittää sen selaimen tarjoajan push-palvelulle. Tämä palvelu herättää selaimen ja näyttää ilmoituksen. Koska operaatio ei koskaan estä kutsuvaa säiettä, voit kutsua sitä napsautustapahtumasta, aikataulutetusta työstä tai pyyntö-käsittelystä.

Kun ominaisuudet on konfiguroitu, lähettäjä on saatavilla beanina, jonka voit injektoida näkymiin, palveluihin ja aikataulutettuihin työpäiviin. Jos haluat korvata sen, määritä oma PushSender-beanisi.

@Route("/tilaukset")
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- säikeessä, joko näkymässä tai App.run()-menetelmässä, ja käytä sitä sitten mistä tahansa säikeestä. Kaikki lähettäjät jakavat yhden yhteyspoolin push-palveluihin, joten niiden luominen missä tahansa tarvitaan ei aiheuta kustannuksia.

Viesteille, jotka on lähetettävä myöhemmin tai käyttäjän poistuttua, käytä palvelimen aikataulutinta, kuten Springin TaskScheduler. Älä käytä sivuaikataulutinta, kuten Interval, koska se pysähtyy, kun välilehti sulkeutuu.

Viestin kokoaminen

Luo viesti sen otsikolla ja määritä sitten jokainen muu vaihtoehto rakennusohjelmassa:

PushMessage message = PushMessage.create("Tilauksen toimitus")
.setBody("Tilaus #42 on matkalla")
.setIcon("icons://icon-192x192.png")
.setUrl("/tilaukset/42")
.setActions(List.of(new PushAction("seuraa", "Seuraa", "/tilaukset/42/seuranta")))
.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()-kutsu tehdään sovellus-säikeessä, kuten kuuntelijasta, sen palautteet suoritetaan tuolla säikeellä ja voivat päivittää komponentteja. Jos sesio, joka kutsui send(), päättyy ennen kuin vastaus saapuu, palautteet eivät suoriteta, mutta ilmoitus toimitetaan silti.

Send-kutsu odottaa korkeintaan 30 sekuntia push-palvelulta ennen kuin se epäonnistuu UNREACHABLE-tilassa. Käytä setTimeout(Duration) muuttaaksesi aikarajaa jokaiselle lähettäjälle.

VaihtoehtoVaikutus
setBodyAsettaa tekstin, joka näkyy otsikon alapuolella
setIconAsettaa kuvan, joka näkyy ilmoituksen mukana. Se hyväksyy absoluuttiset URL-osoitteet ja icons://- ja ws://-protokollat. Katso Varat. Se ei hyväksy context://-protokollaa, koska push-palvelut rajoittavat viestin 4 kt:n
setUrlAsettaa sivun, joka avautuu, kun käyttäjä napsauttaa ilmoitusta. Suhteelliset URL-osoitteet ratkaistaan sovelluksen juurta vastaan. Jos URL-osoitetta ei aseteta, avautuu sovelluksen juuri
setActionsAsettaa ilmoituksessa näytettävät painikkeet, joilla on erilliset URL-osoitteet jokaiselle painikkeelle. Katso Selaimen tuki
setTagAsettaa tunnistavan tagin. Jos ilmoitus, joka on näkyvissä, on sama tagi, uusi ilmoitus korvataan sillä
setSilentNäyttää ilmoituksen ilman ääntä tai tärinää
setTimeToLiveAsettaa, kuinka kauan push-palvelu säilyttää viestin offline-laitteelle, enintään neljä viikkoa
setUrgencyKäyttää PushUrgency:a, jotta laite voi viivästyttää alhaisen kiireen viestejä ja säästää akkua
setTopicKorvataan viesti, joka on edelleen odottamassa push-palvelussa, kun molemmat viestit sisältävät saman aiheen. Aiheilla voi olla enintään 32 merkkiä, jotka ovat turvallisia URL-osoitteissa

Kun välilehti näyttää jo sivun, napsauttaminen ilmoituksessa keskittyy sovellukseen. Muuten sivu avautuu uuteen välilehteen. Napsauttaessa ilmoituspainiketta avautuu sen URL-osoite 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, pushia ei voida käyttää hiljaisiin tietopäivityksiin.

Epäonnistumistila

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

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

Poista vanhentuneet tilaukset jokaisen lähetyksen yhteydessä:

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

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

Push-palvelut peruutavat tilauksia laiskasti. Ne hyväksyvät edelleen ensimmäisen viestin sen jälkeen, kun käyttäjä peruuttaa, mutta se ei mene minnekään. Seuraava viesti ilmoittaa SUBSCRIPTION_EXPIRED. Hyväksytyn lähetyksen merkki on se, että viesti saapui push-palveluun, ei että käyttäjä näki sen.

Selaimen tuki

Kaikki suuret työpöytä- ja mobiiliselaimet näyttävät push-ilmoituksia tilaamisen jälkeen. Pidä mielessä seuraavat rajoitukset:

  • iPhone- ja iPad-laitteella push toimii vain verkko-sovelluksille, jotka on lisätty Käynnistysnäyttöön iOS 16.4 tai uudemmassa. Safari-välilehdessä subscribe() ilmoittaa UNSUPPORTED. Katso Asennettavat sovellukset vaadittaessa sovellusmanifesteja varten.
  • Safari ei näytä ilmoituspainikkeita. Se näyttää viestejä toiminnolla niiden painiketta, mutta napsauttamalla ilmoitusta avaa silti viestin URL-osoitteen.
  • Android- ja iOS-web-näkymät eivät näytä ilmoituksia.

Selaimen yksityiskohtia katso MDN showNotification-yhteensopivuustaulukosta.

Täydellinen esimerkki

Seuraava näkymä tilaa ja peruuttaa selaimen, tallentaa tilaukset muistiin ja lähettää viestin jokaiselle tallennetulle tilaukselle. Se voi lähettää välittömästi tai odottaa kahdeksan sekuntia käyttämällä Springin TaskScheduler:ia, joka mahdollistaa välilehden sulkemisen ennen ilmoituksen saapumista. Sovellusluokka käyttää @EnableScheduling-asetusta tehdäksesi aikatauluttajan saatavaksi.

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
? "Ilmoituksia 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 kahdeksan sekunnin päästä, 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 tilaus");
});

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