Push-bericht in de browser ontvangen

Noud van Kruysbergen
0

Inhoudsopgave

Op een smartphone is een push-bericht de gewoonste zaak van de wereld. Browsers kunnen dergelijke berichten nu ook ontvangen en met de standaardmiddelen van het besturingssysteem laten zien. Daarmee wordt de lacune tussen apps en webapplicaties weer wat kleiner.

Websites kunnen dankzij het tonen van berichten uit een browservenster breken en (hopelijk) belangrijke en interessante meldingen geven: messenger-nieuws van vrienden, spoedmeldingen en sportresultaten, beurskoersen, aanbiedingen en veel meer. We laten aan de hand van een voorbeeld zien hoe browsers pushberichten kunnen verwerken. De voorbeeldcode en andere informatie kun je ook bekijken.

Er bestaat al een paar jaar een handige interface voor eenvoudige berichten: de Notifications-API. Je hoeft als webdeveloper niet al teveel te doen om die te kunnen gebruiken:

<form action=”#”>
<textarea></textarea>
<button>Bericht sturen</button>
</form>
<script src=”notification.js”>
</script>

Als je bij de code hieronder op de knop drukt, moet het script dan de inhoud van <textarea> als pop-up laten zien:

js
document.querySelector('button').
addEventListener('click', ev => {
ev.preventDefault();
if (!('Notification' in window))
throw new Error('Geen Push!');
Notification.requestPermission
(permission => {
if (permission !== 'granted')
{
alert('Geen toestemming');
return;
}
const msg = new
Notification('Bericht', {
body: document
.querySelector('textarea')
.value, icon: 'ct.png'
});
msg.onclick = ev =>
alert('Bericht aangeklikt!');
});
});

Daarbij onderdrukt preventDefault() de wens van de browser om het formulier te verzenden tot er een click-event van de button komt. Na een controle of de browser met de Notification-API kan werken, vraagt requestPermission() toestemming als de gebruiker die bij een eerder bezoek niet al gegeven heeft. Niet elke website mag je immers storen met allerlei irritante zinloze berichten. Je kunt de actie die daarna uitgevoerd moet worden dan net als hier als callback of via een promise (requestPermission().then(…)) aangeven.

Het bericht zelf ontstaat door new Notification(). Dat verwacht twee argumenten: een string als titel en een object met de details. Daarin moet je vooral een body-tekst en een icon in PNG-formaat opgeven. Sommige browsers laten ook een image zien of spelen een zelf gedefinieerde vibratie (vibrate) af. Vier event-handlers begeleiden de berichten door hun levenscyclus: onshow, onclick, onclose en onerror.

De berichten verschijnen in de vorm van een systeemdialoog buiten de browser. Bij Windows duiken ze normaal gesproken op in de rechter benedenhoek van de desktop. De browser of het betreffende tabblad hoeven daarbij niet op de voorgrond te staan om het te laten werken. Als je dat niet gelooft, bouw je een vertraging in bij de berichtgeving door in de inhoud van de bovenstaande functie eenvoudigweg een setTimeout() in te stellen. Dat geeft je genoeg tijd om voor het ontvangen van een bericht naar een ander venster of tabblad over te schakelen.

Het aanklikken van een bericht haalt de bijbehorende website naar de voorgrond. Met behulp van onclick zijn daar net als in het voorbeeld extra acties aan te koppelen. Dat werkt bij alle moderne desktopbrowsers (en dus niet bij Internet Explorer), maar niet met de huidige mobiele browsers.

push-berichten browser notificatie ServiceWorker Progressive Web App JavaScript push.js bericht melding Push Companion

Push-bericht zonder website

Leuk allemaal, maar het essentiële ontbreekt nog. De Notification-API werkt namelijk alleen als een gebruiker de betreffende website geopend heeft. Als je niet meerdere tientallen tabbladen in de browser open wilt hebben staan, heb je aan deze manier van berichten versturen dan ook niet veel. Dat is ook de reden dat de Notification-API nog geen al te groot succes is.

Wat je eigenlijk wilt hebben, zijn push-berichten zoals je die van mobiele apps kent: nadat je daar toestemming voor gegeven hebt, krijg je berichten zodra de aanbieders van de app of website dat zinnig lijkt. Maar hoe doe je dat met een browser? Zelfs als de pushdienst op magische wijze zou weten naar welke computer hij zijn berichten zou moeten sturen, kan de bijbehorende website geen code uitvoeren zonder dat hij geopend is.

Verbazingwekkend genoeg is precies dat laatste nu wel mogelijk. Het uitgangspunt daarvoor is een doorontwikkeling van het boven gebruikte HTML-formulier:

<form action=”#”>
<label>
Sleutel:
<input type=”text”>
</label>
<button disabled>
Pushberichten niet mogelijk
</button>
<output></output>
</form>
<script src=”push.js”></script>

De in eerste instantie gedeactiveerde <button> dient hier voor het abonneren op de pushberichten, die later van de server zullen komen.

ServiceWorkers zijn een van de technische vernieuwingen die pushberichten mogelijk maken. De in JavaScript geschreven ServiceWorkers fungeren als een soort proxy tussen de browser en de server die ze oorspronkelijk geïnstalleerd heeft. Ze reageren op requests, ook als de betreffende website niet geopend is.

ServiceWorkers zijn de kern van de zogeheten Progressive Web Apps (PWS), die voor websites enkele mogelijkheden ontsluiten die voorheen alleen voor smartphone-apps beschikbaar waren – bijvoorbeeld een individueel programmeerbare caching met offline-functie of zelfs het ontvangen van pushberichten. Behalve bij tests op een localhost is er wel een HTTPS-verbinding voor nodig. Het eerste wat je dan ook moet doen, is een ServiceWorker installeren:

js
'use strict';
const btnTexts = [
'Abonneren',
'Abonnement beëindigen'
];
const btn = document.
querySelector('button');
let worker = null;
let isSubscribed = false;
if ('PushManager' in window) {
navigator.serviceWorker.
register('worker.js')
.then(reg => { /* to do ... */ })
.catch(err => console.error(err));
}

Eerst definieer je de opschriften van de button voor het beginnen en beëindigen van een pushabonnement en regel je toegang tot de button. De variabele worker bewaart de registratie van een ServiceWorker, isSubscribed bevat de abonnementsstatus.

Om te testen of een browser de benodigde capaciteiten heeft, vraagt de code naar de PushManager-interface van de Push-API. Op dit moment antwoorden alleen Chromium en Firefox op de desktop en mobiele apparaten positief op die vraag, maar Safari en Edge inclusief de Microsoft Store zullen volgen. De browser registreert enkel het scriptbestand worker.js als ServiceWorker. Daar is in eerste instantie een leeg bestand voldoende voor. Als dat werkt, levert de promise de Worker-registratie terug. Daarmee kun je de status van het pushabonnement opvragen:

js
reg => {
worker = reg;
btn.disabled = false;
worker.pushManager.getSubscription()
.then(subscription => {
if (subscription === null) {
btn.textContent = btnTexts[0];
} else {
isSubscribed = true;
btn.textContent = btnTexts[1];
}
});
}

De ServiceWorker-registratie wordt daarbij opgeslagen in de globale variabele worker, waarna de button voor het abonneren aanklikbaar wordt. Met getSubscription() kun je vervolgens controleren of een gebruiker zich al geabonneerd heeft op pushmeldingen voor het huidige domein – wat je vervolgens kunt zien aan de benaming van de button en aan de inhoud van de variabele isSubscribed. De webpagina moet dan een aanklikbare button met ‘Abonneren’ erop laten zien.

Testen push-bericht

Met de ontwikkeltools kun je al een klein bericht klaarzetten – maar niet versturen.Daarvoor moet je namelijk eerste met de Worker aan de slag. Zet de volgende code in worker.js:

js
'use strict';
self.addEventListener('push', ev => {
const title = 'Bericht!';
let text = 'Tekst ...';
if (ev.data !== null)
text = ev.data.text();
ev.waitUntil(self.registration.
showNotification(title,
{body: text}));
});

Daarbij heeft self betrekking op de globale scope in ServiceWorkers en vervangt dat hier window. Bij het ontvangen van een pushbericht komt de bij de ServiceWorker-registratie behorende methode showNotification() in actie. Die volgt dezelfde regels als de inmiddels al bekende Notifications-API. De functie waitUntil() is een niet per se benodigde voorzorgsmaatregel waarmee de ServiceWorker de browser vraagt niet voortijdig beëindigd te worden.

Net als bij het vorige voorbeeld kun je een actie laten uitvoeren zodra een gebruiker op een pushbericht klikt. Zonder toegang tot window is dat echter wel iets complexer:

js
self.addEventListener
('notificationclick', ev => {
ev.notification.close();
ev.waitUntil(
self.clients.
openWindow('https://ct.nl/')
);
});

Het notificationclick-event staat toegang tot het bericht toe. Om een url te openen, heb je de openWindow()-methode van de Clients-interface nodig, die de ServiceWorker beschikbaar stelt in de variabele self.client. Meer hoef je met de ServiceWorker niet te doen.

Bij de Chromium-browser kun je bij de ‘Hulpprogramma’s voor ontwikkelaars’ op het tabblad ‘Application’ dan een pushbericht laten versturen. Bij de desktopbrowsers moeten die dan rechtsonder verschijnen. Firefox verstuurt vanaf de pagina about:debugging#workers een tekstloos bericht.

push-berichten browser notificatie ServiceWorker Progressive Web App JavaScript push.js bericht melding Push Companion ontwikkelaar lokaal

De websitespecifieke notificatie-instellingen zijn via de adresbalk toegankelijk. Onderin kun je bij de ontwikkelaarstools lokale pushberichten testen.

Sleutels uitwisselen

Bij de volgende stap moet het front-end het abonnement van de pushberichten regelen. Breidt push.js uit om muisklikken op de button te verwerken:

js
button.addEventListener('click',
ev => {
ev.preventDefault();
if (worker === null) return;
if (isSubscribed) {
// unsubscribe ...
} else {
// subscribe ...
}
});

Als het daarvoor niet gelukt is om een ServiceWorker te installeren (worker === null), dan moet je eerst wat weten over het gecompliceerde verloop bij het versturen van pushberichten. De betreffende webdienst stuurt je die namelijk niet rechtstreeks, maar doet dat via een derde partij. Browsers nemen na het starten contact op met een vast geïmplementeerde pushservice en houden de TCP-verbinding open. Op die manier heeft de pushdienst altijd de mogelijkheid data naar een gebruiker te sturen. De pushservice voor alle Chromium-browsers verstuurt de berichten via fcm.googleapis.com, de Mozillavariant bevindt zich op updates.push.services.mozilla.com. Pushdiensten hebben een interface die zich houdt aan de standaard RFC 8030.

Als je een abonnement neemt, stuurt de browser dat naar zijn pushservice en krijgt als antwoord dan een lange individuele ‘endpoint’-url. Via die url ontvangt de pushdienst berichten van de applicatieserver, die hij vervolgens naar alle gebruikers pusht.

 

Doorlezen is gratis, maar eerst even dit:

Dit artikel is met grote zorg samengesteld door de redactie van c’t magazine – het meest toonaangevende computertijdschrift van Nederland en België. Met zeer uitgebreide tests en praktische workshops biedt c’t de diepgang die je nergens online vindt.

Bekijk de abonnementen   Lees eerst verder

Oorspronkelijk was het de bedoeling dat een endpoint-url voldoende was voor het versturen (bij Mozilla’s pushdienst kan dat nog steeds), maar daarna heeft men meerdere manieren voor het versleutelen en authenticeren toegevoegd. Daarom moet een browser bij het afsluiten van een abonnement de openbare sleutel van de applicatieserver aan de pushdienst sturen (zie het kader hieronder).


Complexe push-berichten

Voordat een website pushberichten kan verzenden, moeten er nogal wat hobbels genomen worden met de pushdienst en de browser.

push-berichten browser notificatie ServiceWorker Progressive Web App JavaScript push.js bericht melding schema uitleg diagram

Vooraf
0: browser maakt via WebSockets contact met de pushdienst

Abonnement
1: browser vraagt data van webserver
2: webserver stuurt webpagina inclusief openbare sleutel
3: webpagina registreert ServiceWorker
4: webpagina abonneert pushberichten van pushdienst en verzendt serversleutel
5: pushdienst stuurt endpoint terug
6: webpagina stuurt endpoint, openbare clientsleutel en auth-geheim naar webserver

Berichten versturen
7: webserver stuurt versleuteld en door VAPID ondertekend bericht naar pushdienst
8: pushdienst bevestigt ontvangst
9: pushdienst stuurt bericht via WebSocket (of HTTP/2-Push) naar browser, waar hij continu mee verbonden is; de browser ontsleutelt het bericht, controleert het auth-geheim en stuurt de content door naar de ServiceWorker
10: de ServiceWorker toont het bericht via de Notification-API


 

Zolang je nog geen eigen back-end hebt, kun je de Push Companion gebruiken. Het enige verschil met een echt applicatie-back-end is dat de processen niet automatisch, maar handmatig gestart worden. Als je die pagina opent, staat daar een ongeveer 65 bytes lange openbare sleutel. Die kopieer je voor het afsluiten van een abonnement in het invoerveld van het bij aanvang gedefinieerde formulier.

Het format van die sleutel is een licht gemodificeerde variant van Base64, die in url’s geen problemen veroorzaakt. De pushdienst verwacht de sleutel daarentegen in de vorm van een getypeerde array – die moet je dus converteren:

js
function urlB64ToUint8(b64String) {
const padding = '='.repeat(
(4 - b64String.length % 4) % 4);
const b64 = (b64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const raw = atob(b64);
const outputArray =
new Uint8Array(raw.length);
for(let i = 0; i < raw.length; ++i)
outputArray[i] =
raw.charCodeAt(i);
return outputArray;
}

Die functie converteert de url-bestendige Base64 naar normaal, extraheert daar de bytecode uit en zet die in een array van unsigned 8-bit integers.

Abonneren

Daarmee is alles klaar om je te kunnen abonneren:

js
const pubkey =
document.querySelector('input');
const output =
document.querySelector('output');
btn.addEventListener('click', ev => {
if (isSubscribed) {
// ...
} else {
worker.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey:
urlB64ToUint8(pubkey.value)
})
.then(subscr => {
output.textContent =
JSON.stringify(subscr);
btn.textContent = btnTexts[1];
isSubscribed = true;
})
}
});

De methode pushManager.subscribe() van de ServiceWorker-registratie is verantwoordelijk voor het abonnement. Als argument wordt een object met twee opties verwacht: de geconverteerde applicationServerKey en de toezegging dat alle pushdata voor de gebruikers zichtbaar worden (userVisibleOnly). Er is wel gedacht aan onzichtbare pushdata, maar de browsers staan die uit security-overwegingen niet toe.

Als alles werkt, krijgt de promise een subscription-object terug. De inhoud daarvan moet je doorgeven aan het backend. In dit geval is Push Companion het back-end, en daar communiceer je mee via copy&paste. Daarom levert de code de subscription als string in het <output>– element. Als laatste moet je dan nog de button-tekst en de status van isSubscribed bijwerken.

Voordat je aan het testen gaat, moet je eerst nog even het unsubscribe-deel compleet maken:

js
if (isSubscribed) {
worker.pushManager.getSubscription()
.then(subscr => {
if (subscr)
subscr.unsubscribe();
})
.then(() => {
output.textContent = '';
button.textContent = btnTexts[0];
isSubscribed = false;
});
} else {...}

De functie getSubscription() ken je al. Op het van die promise teruggekregen subscription-object pas je de unsubscribe()-methode toe. Als laatste moet je dan net als voorheen de HTML en isSubscribed bijwerken.

Klaar om te verzenden

Als je dan de openbare sleutel van Push Companion kopieert naar het invoerveld en in de browser klikt op ‘Abonneren’, dan verschijnt er JSON-code in <output>. Die bevat de url van endpoint, een expirationTime voor de geldigheid van een abonnement (in ons geval was dat altijd null) en twee door de browser aangemaakte Base64-strings: de door de browser gegeneerde sleutel p256dh, die later het bericht zal ontcijferen, en het auth-geheim voor het authenticeren.

Die uitvoer voeg je in bij Push Companion. Let er daarbij op dat daar dezelfde sleutel ingesteld is als op je webpagina. Na een druk op de knop verstuurt de website een pushbericht naar het pushservice- endpoint, dat het doorgeeft aan de door jou geprogrammeerde ServiceWorker.

Let er wel even op dat als je bij het uitproberen het ontvangen van berichten te vaak afwijst, dat de browser ze dan zonder verder commentaar gaat verwerpen. Chromium-browsers vragen dat maar drie keer, daarna moet je de websitespecifieke instellingen veranderen.

Als je zelf pushberichten wilt versturen, helpt Push Companion je niet verder. Het maken van een eigen back-end is echter geen sinecure. De daarbij benodigde processen worden zelfs door Google Tutorials als nogal een gedoe bestempeld, met name omdat mogelijke problemen moeilijk te diagnosticeren zijn. In het tweede deel van deze push-tutorial zie je hoe je dat met de op Node.js gebaseerde tool web-push kunt doen.

(Herbert Braun, c’t magazine)

 

Lees het tweede deel van deze push-tutorial in c't sep/2018

Deel dit artikel

Noud van Kruysbergen
Noud van KruysbergenNoud heeft de 'American Dream' doorlopen van jongste bediende tot hoofdredacteur van c't, waar hij zo veel mogelijk de diepgang, betrouwbaarheid en diversiteit wil bewaken.

Lees ook

Smartphone levensduur verlengen: deze tips en tricks helpen je!

Hoe zorg je ervoor dat je telefoon langer meegaat? In dit artikel geven we je tips om de levensduur van je smartphone te verlengen en hem dus langer t...

Windows Rekenmachine handleiding met tips & tricks

Op het eerste gezicht lijkt de Rekenmachine-app van Windows op een conventionele bureaubladrekenmachine. Maar hij biedt meer dan dat en daarom is het ...

0 Praat mee
avatar
  Abonneer  
Laat het mij weten wanneer er