W tym ćwiczeniu z programowania utworzysz serwer powiadomień push. Serwer będzie zarządzać listą subskrypcji push i wysyłać do nich powiadomienia.
Kod po stronie klienta jest już gotowy. W tym ćwiczeniu będziesz pracować nad funkcjami po stronie serwera.
Zmodyfikuj przykładową aplikację i wyświetl ją w nowej karcie
Powiadomienia z umieszczonej aplikacji Glitch są automatycznie blokowane, więc nie możesz wyświetlić podglądu aplikacji na tej stronie. Zamiast tego:
- Kliknij Remix to Edit (Zmiksuj do edycji), aby umożliwić edycję projektu.
- Aby wyświetlić podgląd strony, kliknij Wyświetl aplikację. Następnie kliknij Pełny ekran.
Aplikacja na żywo otworzy się w nowej karcie Chrome. Aby ponownie wyświetlić kod, kliknij Wyświetl źródło w umieszczonej wersji błędu.
W trakcie wykonywania ćwiczeń w tym laboratorium kodu wprowadzaj zmiany w kodzie w osadzonym na tej stronie Glitchu. Odśwież nową kartę z aplikacją na żywo, aby zobaczyć zmiany.
Poznaj aplikację startową i jej kod
Zacznij od przejrzenia interfejsu użytkownika aplikacji.
W nowej karcie Chrome:
Aby otworzyć Narzędzia dla programistów, naciśnij `Control+Shift+J` (lub `Command+Option+J` na Macu). Kliknij kartę Konsola.
Spróbuj kliknąć przyciski w interfejsie (wynik znajdziesz w konsoli deweloperskiej Chrome).
Zarejestruj skrypt service worker – rejestruje skrypt service worker w zakresie adresu URL projektu Glitch. Wyrejestruj skrypt service worker powoduje usunięcie skryptu service worker. Jeśli jest do niego podłączona subskrypcja push, to taka subskrypcja również zostanie dezaktywowana.
Subskrybuj powiadomienia push tworzy subskrypcję push. Jest ona dostępna tylko wtedy, gdy skrypt service worker został zarejestrowany, a w kodzie klienta znajduje się stała
VAPID_PUBLIC_KEY
(więcej informacji na ten temat znajdziesz w dalszej części tego artykułu), dlatego nie można jeszcze jej kliknąć.Gdy masz aktywną subskrypcję push, powiadomienie o bieżącej subskrypcji prosi serwer o wysłanie powiadomienia do punktu końcowego.
Powiadomij wszystkie subskrypcje powoduje, że serwer wysyła powiadomienie do wszystkich punktów końcowych subskrypcji w swojej bazie danych.
Pamiętaj, że niektóre z tych punktów końcowych mogą być nieaktywne. Zawsze istnieje możliwość, że subskrypcja zniknie, zanim serwer wyśle powiadomienie.
Zobaczmy, co dzieje się po stronie serwera. Aby zobaczyć komunikaty z kodu serwera, sprawdź log Node.js w interfejsie Glitch.
W aplikacji Glitch kliknij Narzędzia -> Logi.
Prawdopodobnie zobaczysz komunikat podobny do
Listening on port 3000
.Jeśli w interfejsie aplikacji na żywo klikniesz Powiadom obecną subskrypcję lub Powiadom wszystkie subskrypcje, zobaczysz też ten komunikat:
TODO: Implement sendNotifications()
Endpoints to send to: []
Przyjrzyjmy się teraz kodom.
public/index.js
zawiera kompletny kod klienta. Wykonuje wykrywanie funkcji, rejestruje i odrejestruje skrypt service worker oraz kontroluje subskrypcję powiadomień push przez użytkownika. Serwer wysyła też informacje o nowych i usuniętych subskrypcjach.Ponieważ będziesz pracować tylko nad funkcjami serwera, nie będziesz edytować tego pliku (poza wypełnianiem stałej
VAPID_PUBLIC_KEY
).public/service-worker.js
to prosty skrypt service worker, który rejestruje zdarzenia push i wyświetla powiadomienia./views/index.html
zawiera interfejs aplikacji..env
zawiera zmienne środowiskowe, które Glitch wczytuje na serwer aplikacji podczas uruchamiania. W polu.env
wpisz dane uwierzytelniające na potrzeby wysyłania powiadomień.server.js
to plik, w którym będziesz wykonywać większość czynności podczas tego ćwiczenia.Kod początkowy tworzy prosty serwer WWW Express. Masz 4 elementy TODO oznaczone w komentarzach kodu symbolem
TODO:
. Czynności, które musisz wykonać:W tym ćwiczeniu będziesz kolejno rozwiązywać te zadania.
Generowanie i wczytywanie szczegółów VAPID
Pierwszym krokiem jest wygenerowanie szczegółów VAPID, dodanie ich do zmiennych środowiskowych Node.js i zaktualizowanie kodu klienta i serwera za pomocą nowych wartości.
Tło
Aby subskrybować powiadomienia, użytkownicy muszą ufać tożsamości aplikacji i jej serwera. Użytkownicy muszą też mieć pewność, że powiadomienie pochodzi z tej samej aplikacji, która skonfigurowała subskrypcję. Musi też mieć pewność, że nikt inny nie może odczytać treści powiadomienia.
Protokół, który zapewnia bezpieczeństwo i prywatność powiadomień push, nazywa się Voluntary Application Server Identification for Web Push (VAPID). VAPID używa kryptografii klucza publicznego do weryfikacji tożsamości aplikacji, serwerów i punktów końcowych subskrypcji oraz do szyfrowania treści powiadomień.
W tej aplikacji do generowania kluczy VAPID oraz szyfrowania i wysyłania powiadomień użyjesz pakietu npm web-push.
Implementacja
W tym kroku wygeneruj parę kluczy VAPID dla aplikacji i dodaj je do zmiennych środowiskowych. Wczytaj zmienne środowiskowe na serwerze i dodaj klucz publiczny jako stałą w kodzie klienta.
Użyj funkcji
generateVAPIDKeys
bibliotekiweb-push
, aby utworzyć parę kluczy VAPID.W pliku server.js usuń komentarze z podanych niżej linii kodu:
server.js
// Generate VAPID keys (only do this once).
/*
* const vapidKeys = webpush.generateVAPIDKeys();
* console.log(vapidKeys);
*/
const vapidKeys = webpush.generateVAPIDKeys();
console.log(vapidKeys);Gdy Glitch uruchomi ponownie Twoją aplikację, wygenerowane klucze zostaną wyprowadzone do dziennika Node.js w interfejsie Glitch (nie do konsoli Chrome). Aby wyświetlić klucze VAPID, w interfejsie Glitch wybierz Narzędzia -> Dzienniki.
Pamiętaj, aby skopiować klucz publiczny i prywatny z tej samej pary kluczy.
Usterka uruchamia ponownie aplikację za każdym razem, gdy edytujesz kod, więc pierwsza para wygenerowanych kluczy może zniknąć z widoku w miarę pojawiania się kolejnych danych wyjściowych.
W pliku .env skopiuj i wklej klucze VAPID. Umieść klucze w cudzysłowie (
"..."
).W polu
VAPID_SUBJECT
możesz wpisać"mailto:test@test.test"
..env
# process.env.SECRET
VAPID_PUBLIC_KEY=
VAPID_PRIVATE_KEY=
VAPID_SUBJECT=
VAPID_PUBLIC_KEY="BN3tWzHp3L3rBh03lGLlLlsq..."
VAPID_PRIVATE_KEY="I_lM7JMIXRhOk6HN..."
VAPID_SUBJECT="mailto:test@test.test"W pliku server.js ponownie wyłącz te 2 wiersze kodu, ponieważ klucze VAPID trzeba wygenerować tylko raz.
server.js
// Generate VAPID keys (only do this once).
/*
const vapidKeys = webpush.generateVAPIDKeys();
console.log(vapidKeys);
*/
const vapidKeys = webpush.generateVAPIDKeys();
console.log(vapidKeys);W pliku server.js wczytaj informacje VAPID ze zmiennych środowiskowych.
server.js
const vapidDetails = {
// TODO: Load VAPID details from environment variables.
publicKey: process.env.VAPID_PUBLIC_KEY,
privateKey: process.env.VAPID_PRIVATE_KEY,
subject: process.env.VAPID_SUBJECT
}Skopiuj i wklej też klucz public do kodu klienta.
W pliku public/index.js wpisz tę samą wartość parametru
VAPID_PUBLIC_KEY
, którą skopiowano do pliku .env:public/index.js
// Copy from .env
const VAPID_PUBLIC_KEY = '';
const VAPID_PUBLIC_KEY = 'BN3tWzHp3L3rBh03lGLlLlsq...';
````
Wdrożenie funkcji wysyłania powiadomień
Tło
W tej aplikacji do wysyłania powiadomień będziesz używać pakietu npm web-push.
Ten pakiet automatycznie szyfruje powiadomienia, gdy zostanie wywołana funkcja webpush.sendNotification()
, więc nie musisz się tym martwić.
Web-push akceptuje wiele opcji powiadomień – na przykład możesz dołączyć nagłówki do wiadomości i określić kodowanie treści.
W tym ćwiczeniu użyjesz tylko 2 opcji zdefiniowanych za pomocą tych linii kodu:
let options = {
TTL: 10000; // Time-to-live. Notifications expire after this.
vapidDetails: vapidDetails; // VAPID keys from .env
};
Opcja TTL
(czas życia) ustawia limit czasu wygaśnięcia powiadomienia. Dzięki temu serwer nie będzie wysyłać powiadomienia do użytkownika, gdy nie będzie ono już aktualne.
Opcja vapidDetails
zawiera klucze VAPID załadowane z zmiennych środowiskowych.
Implementacja
W pliku server.js zmodyfikuj funkcję sendNotifications
w ten sposób:
server.js
function sendNotifications(database, endpoints) {
// TODO: Implement functionality to send notifications.
console.log('TODO: Implement sendNotifications()');
console.log('Endpoints to send to: ', endpoints);
let notification = JSON.stringify(createNotification());
let options = {
TTL: 10000, // Time-to-live. Notifications expire after this.
vapidDetails: vapidDetails // VAPID keys from .env
};
endpoints.map(endpoint => {
let subscription = database[endpoint];
webpush.sendNotification(subscription, notification, options);
});
}
Funkcja webpush.sendNotification()
zwraca obietnicę, więc możesz łatwo dodać obsługę błędów.
W pliku server.js ponownie zmodyfikuj funkcję sendNotifications
:
server.js
function sendNotifications(database, endpoints) {
let notification = JSON.stringify(createNotification());
let options = {
TTL: 10000; // Time-to-live. Notifications expire after this.
vapidDetails: vapidDetails; // VAPID keys from .env
};
endpoints.map(endpoint => {
let subscription = database[endpoint];
webpush.sendNotification(subscription, notification, options);
let id = endpoint.substr((endpoint.length - 8), endpoint.length);
webpush.sendNotification(subscription, notification, options)
.then(result => {
console.log(`Endpoint ID: ${id}`);
console.log(`Result: ${result.statusCode} `);
})
.catch(error => {
console.log(`Endpoint ID: ${id}`);
console.log(`Error: ${error.body} `);
});
});
}
Obsługa nowych subskrypcji
Tło
Oto, co się dzieje, gdy użytkownik subskrybuje powiadomienia push:
Użytkownik klika Subskrybuj powiadomienia push.
Klient używa stałej
VAPID_PUBLIC_KEY
(publicznego klucza VAPID serwera) do wygenerowania unikalnego obiektusubscription
dla danego serwera. Obiektsubscription
wygląda tak:{
"endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9...",
"expirationTime": null,
"keys":
{
"p256dh": "BNYDjQL9d5PSoeBurHy2e4d4GY0sGJXBN...",
"auth": "0IyyvUGNJ9RxJc83poo3bA"
}
}Klient wysyła żądanie
POST
do adresu URL/add-subscription
, w tym subskrypcję w postaci ciągu znaków JSON w treści.Serwer pobiera z treści żądania POST ciąg znaków
subscription
, przetwarza go z powrotem na format JSON i dodaje do bazy danych subskrypcji.Baza danych przechowuje subskrypcje, używając ich własnych punktów końcowych jako klucza:
{
"https://fcm...1234": {
endpoint: "https://fcm...1234",
expirationTime: ...,
keys: { ... }
},
"https://fcm...abcd": {
endpoint: "https://fcm...abcd",
expirationTime: ...,
keys: { ... }
},
"https://fcm...zxcv": {
endpoint: "https://fcm...zxcv",
expirationTime: ...,
keys: { ... }
},
}
Nowa subskrypcja jest teraz dostępna dla serwera do wysyłania powiadomień.
Implementacja
Żądania dotyczące nowych subskrypcji trafiają do ścieżki /add-subscription
, która jest adresem URL POST. W pliku server.js zobaczysz fragment obsługi trasy:
server.js
app.post('/add-subscription', (request, response) => {
// TODO: implement handler for /add-subscription
console.log('TODO: Implement handler for /add-subscription');
console.log('Request body: ', request.body);
response.sendStatus(200);
});
W Twojej implementacji ten moduł musi:
- Pobierz nową subskrypcję z treści żądania.
- Uzyskać dostęp do bazy danych o aktywnych subskrypcjach.
- Dodaj nową subskrypcję do listy aktywnych subskrypcji.
Aby obsługiwać nowe subskrypcje:
W pliku server.js zmodyfikuj przetwarzacz trasy dla
/add-subscription
w ten sposób:server.js
app.post('/add-subscription', (request, response) => {
// TODO: implement handler for /add-subscription
console.log('TODO: Implement handler for /add-subscription');
console.log('Request body: ', request.body);
let subscriptions = Object.assign({}, request.session.subscriptions);
subscriptions[request.body.endpoint] = request.body;
request.session.subscriptions = subscriptions;
response.sendStatus(200);
});
Obsługa anulowania subskrypcji
Tło
Serwer nie zawsze wie, kiedy subskrypcja staje się nieaktywna. Na przykład subskrypcja może zostać usunięta, gdy przeglądarka wyłączy usługę.
Serwer może jednak wykryć subskrypcje anulowane w interfejsie aplikacji. W tym kroku wdrożysz funkcję usuwania subskrypcji z bazy danych.
Dzięki temu serwer nie wysyła dużej liczby powiadomień do nieistniejących punktów końcowych. Oczywiście w przypadku prostej aplikacji testowej nie ma to większego znaczenia, ale na większą skalę staje się to istotne.
Implementacja
Prośby o anulowanie subskrypcji trafiają na adres URL POST /remove-subscription
.
Ignorowany moduł obsługi trasy w pliku server.js wygląda tak:
server.js
app.post('/remove-subscription', (request, response) => {
// TODO: implement handler for /remove-subscription
console.log('TODO: Implement handler for /remove-subscription');
console.log('Request body: ', request.body);
response.sendStatus(200);
});
W Twojej implementacji ten moduł musi:
- Z treści żądania pobierz punkt końcowy anulowanej subskrypcji.
- Uzyskać dostęp do bazy danych o aktywnych subskrypcjach.
- Anulowana subskrypcja zostanie usunięta z listy aktywnych subskrypcji.
Treść żądania POST z klienta zawiera punkt końcowy, który należy usunąć:
{
"endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9..."
}
Aby anulować subskrypcję:
W pliku server.js zmodyfikuj moduł obsługi trasy
/remove-subscription
w ten sposób:server.js
app.post('/remove-subscription', (request, response) => {
// TODO: implement handler for /remove-subscription
console.log('TODO: Implement handler for /remove-subscription');
console.log('Request body: ', request.body);
let subscriptions = Object.assign({}, request.session.subscriptions);
delete subscriptions[request.body.endpoint];
request.session.subscriptions = subscriptions;
response.sendStatus(200);
});