Datasobota, 5 września 2026 Czas05:10:47
← Aplikacje webowe

Our Voice: aplikacja PWA od pomysłu do wdrożenia

Średni

Po co się tego uczymy?

Ta lekcja pokazuje cały proces tworzenia prawdziwej aplikacji webowej na przykładzie Our Voice - mobilnego towarzysza wyjazdu chóru do Strasburga. Projekt musiał działać na iPhonie, Androidzie i komputerze, zużywać mało internetu, przechowywać plan offline oraz umożliwiać uczestnikom rozmowę tekstową.

To dobry przykład pracy programisty, ponieważ sam kod był tylko częścią zadania. Równie ważne były: zebranie wymagań, ochrona danych, zaplanowanie aktualizacji, testowanie na produkcji i przygotowanie rollbacku.

Teoria

1. Wymagania funkcjonalne

  • prywatny plan podróży, noclegi i telefony alarmowe;
  • osobne konto każdego uczestnika i podpisane wiadomości;
  • czat wyłącznie tekstowy, bez zdjęć i nagrań;
  • automatyczne pojawianie się nowych wiadomości;
  • czerwona plakietka, dźwięk i wibracja po nadejściu wiadomości;
  • powiadomienie Web Push również po zamknięciu aplikacji;
  • instalacja z przeglądarki jako PWA.

2. Wymagania niefunkcjonalne

  • mały transfer - klient pobiera tylko rekordy nowsze od ostatniego identyfikatora;
  • tryb offline dla danych krytycznych;
  • zgodność z WordPressem i istniejącymi kontami;
  • brak sekretów i prywatnych numerów w publicznym materiale dydaktycznym;
  • bezpieczne wdrożenie: kopia bazy, kontrola składni, test i możliwość cofnięcia wersji.

3. Architektura

Our Voice powstało jako niezależna wtyczka WordPress. WordPress odpowiada za użytkowników, sesje, bazę danych i REST API. Frontend jest lekką aplikacją typu SPA napisaną w czystym JavaScript. Manifest i service worker zmieniają stronę w instalowalną PWA.

4. Dane offline i online

Plan, adresy, telefony, CSS i JavaScript mogą zostać zapisane w Cache API. Czat pozostaje funkcją online, ponieważ wiadomości muszą pochodzić z serwera. Service worker stosuje strategię network first: najpierw próbuje pobrać aktualną wersję, a przy braku sieci korzysta z kopii.

5. Bezpieczeństwo

Endpointy czatu wymagają zalogowania i nonce REST. Tekst jest czyszczony, ma limit 500 znaków, a zapytania wykorzystują mechanizmy WordPressa. Publiczny przykład nie zawiera produkcyjnego kodu dostępu, danych SFTP ani numerów uczestników. W prawdziwym systemie wspólny kod powinien być ustawieniem środowiskowym, a nie wartością pokazywaną w repozytorium.

6. Pełny Web Push

Polling działa tylko wtedy, gdy aplikacja jest otwarta lub pozostaje aktywna w tle. Aby wiadomość dotarła również po jej zamknięciu, Our Voice wykorzystuje standard Push API. Po świadomej zgodzie użytkownika przeglądarka tworzy subskrypcję przypisaną do konkretnego urządzenia. Serwer zapisuje ją przy koncie uczestnika, a po dodaniu wiadomości wysyła zaszyfrowany komunikat do wszystkich odbiorców poza autorem.

Klucze VAPID identyfikują serwer wysyłający. Klucz publiczny trafia do aplikacji, natomiast prywatny pozostaje wyłącznie na serwerze. Service worker odbiera zdarzenie push, wyświetla systemowe powiadomienie i po jego dotknięciu otwiera Our Voice.

Schemat

[Uczestnik / PWA]
        |
        | HTTPS + nonce
        v
[WordPress REST API] ---> [Tabela wiadomości]
        |
        +---------------> [Konta WordPress]

[Service Worker] -------> [Cache: plan, telefony, CSS, JS]
        |
        +---------------> [Aktualizacja network first]

[Nowa wiadomość] ------> [Serwer + VAPID]
        |                         |
        |                         v
        +--------------> [Push Service urządzenia]
                                  |
                                  v
                         [Service Worker -> alert]

Przykład z życia

Projekt rozwijano krótkimi iteracjami. Najpierw powstał ekran podróży i tryb offline. Następnie dodano rejestrację uczestników i tabelę czatu. Test dwóch niezależnych sesji ujawnił, że samo wysyłanie wiadomości nie wystarcza - odbiorca musiał wcześniej ponownie wejść do czatu. Rozwiązaniem był lekki polling z parametrem after.

Kolejny test na iPhonie ujawnił agresywne buforowanie ikony ekranu początkowego. Nowa ikona dostała osobną nazwę, rozmiar 180x180 oraz dedykowany znacznik apple-touch-icon. To pokazuje, dlaczego test przeglądarkowy nie zastępuje testu na prawdziwym urządzeniu.

Ostatnim etapem były prawdziwe powiadomienia Web Push. Test wykonano przy zamkniętej aplikacji: wiadomość dotarła na telefon z iOS, a system przekazał ją również na sparowany zegarek. Na wcześniej zainstalowanym Androidzie trzeba było jednorazowo ponownie zezwolić na alerty w profilu, ponieważ stara instalacja nie miała jeszcze zapisanej subskrypcji Push API. Nowe instalacje przechodzą ten krok podczas włączania powiadomień.

Gotową aplikację można zobaczyć pod adresem bitedu.pl/ourvoice/. Dane osobowe widzą wyłącznie zalogowani uczestnicy.

app.js - zgoda i subskrypcja Push API

async function enablePush() {
  const permission = await Notification.requestPermission();
  if (permission !== 'granted') return false;

  const registration = await navigator.serviceWorker.ready;
  const subscription = await registration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: urlBase64ToUint8Array(config.vapidPublicKey)
  });

  const response = await fetch(config.subscriptionUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-WP-Nonce': config.nonce
    },
    body: JSON.stringify(subscription)
  });

  return response.ok;
}

Service worker - odbiór powiadomienia

self.addEventListener('push', event => {
  const data = event.data ? event.data.json() : {};
  event.waitUntil(self.registration.showNotification(
    data.title || 'Our Voice',
    {
      body: data.body || 'Nowa wiadomość na czacie',
      icon: '/wp-content/plugins/ourvoice/assets/icon-192.png',
      badge: '/wp-content/plugins/ourvoice/assets/badge-96.png',
      vibrate: [180, 80, 180],
      data: { url: '/ourvoice/' }
    }
  ));
});

self.addEventListener('notificationclick', event => {
  event.notification.close();
  event.waitUntil(clients.openWindow(event.notification.data.url));
});

Komentarz i wyjaśnienie kodu

  1. Przeglądarka może zapytać o zgodę dopiero w reakcji na świadomy gest użytkownika.
  2. userVisibleOnly oznacza, że każde odebrane zdarzenie push ma być widoczne jako powiadomienie.
  3. Subskrypcja zawiera adres usługi push i klucze urządzenia, dlatego zapisujemy ją tylko dla zalogowanego konta.
  4. Serwer wysyła komunikat z użyciem VAPID, lecz nigdy nie udostępnia klientowi klucza prywatnego.
  5. Service worker działa niezależnie od otwartego okna i obsługuje kliknięcie w alert.

Our Voice łączy dwa mechanizmy: lekki polling aktualizuje otwarty czat, a Web Push informuje o wiadomości przy zamkniętej aplikacji. Push nie zastępuje API czatu — jego treść jest sygnałem, po którym aplikacja pobiera właściwe dane z serwera.

Ćwiczenie samodzielne

Rozbuduj prototyp czatu o Web Push. Zapisz subskrypcję przy koncie użytkownika, pomiń autora podczas wysyłania i usuń z bazy subskrypcję, gdy usługa push odpowie statusem oznaczającym jej wygaśnięcie.

Kryteria sukcesu: alert dociera po zamknięciu PWA, kliknięcie otwiera czat, autor nie otrzymuje własnego powiadomienia, odmowa zgody nie blokuje pozostałych funkcji, a klucz prywatny VAPID nigdy nie trafia do JavaScriptu.

Zadania do pracy własnej

  1. Dodaj do interfejsu czerwoną plakietkę z liczbą nieprzeczytanych wiadomości. Dla wartości większej niż 9 wyświetl „9+”.

  2. Dodaj przycisk „Sprawdź aktualizacje”, który usuwa wyłącznie cache aplikacji, wywołuje update() rejestracji service workera i przeładowuje stronę z parametrem unikającym starej kopii.

  3. Dodaj panel diagnostyczny Web Push pokazujący: stan zgody, obecność aktywnej subskrypcji, datę jej aktualizacji i wynik ostatniej wysyłki — bez ujawniania endpointu ani kluczy użytkownika.

Typowe błędy

  • Odświeżanie tylko przy wejściu do zakładki: odbiorca nie widzi nowych wiadomości. Rozwiązanie: polling oraz odświeżenie po zdarzeniu visibilitychange.
  • Pobieranie całej historii co kilka sekund: niepotrzebny transfer. Rozwiązanie: parametr after i indeks na kolumnie id.
  • Cache REST API: użytkownik może dostać starą odpowiedź. Endpoint czatu należy wyłączyć ze strategii cache.
  • Dźwięk uruchamiany bez gestu: Safari i Chrome mogą go zablokować. Najpierw użytkownik musi nacisnąć przycisk włączający alerty.
  • Sama zgoda bez subskrypcji: status „dozwolone” nie wystarcza. Urządzenie musi jeszcze wykonać pushManager.subscribe(), a serwer zapisać wynik.
  • Test wyłącznie w otwartej aplikacji: może sprawdzić alert lokalny, ale nie pełny Web Push. Właściwy test wykonujemy po zamknięciu PWA i wysłaniu wiadomości z drugiego konta.
  • Stara instalacja po wdrożeniu Push API: ma zgodę, lecz może nie mieć subskrypcji. Należy jednorazowo ponownie włączyć alerty w profilu.
  • Założenie, że iOS działa jak karta Safari: Web Push na iPhonie wymaga PWA dodanej do ekranu początkowego i zgody wywołanej działaniem użytkownika.
  • Zmiana obrazka pod tą samą nazwą: iOS może zachować starą ikonę. Użyj nowej nazwy pliku i ikony 180x180.
  • Brak kopii przed wdrożeniem: awaria staje się trudna do cofnięcia. Przed migracją wykonaj backup bazy i katalogu wtyczki.

Nawiązanie do egzaminu zawodowego

Projekt łączy umiejętności wymagane w INF.04: analizę wymagań, projektowanie aplikacji webowej, programowanie w JavaScript i PHP, pracę z relacyjną bazą danych, REST API, uwierzytelnianie, walidację danych, testowanie i wdrażanie.

W dokumentacji projektu powinny znaleźć się: cel, użytkownicy, wymagania funkcjonalne i niefunkcjonalne, diagram architektury, model danych, opis bezpieczeństwa, scenariusze testowe, instrukcja wdrożenia i plan rollbacku.