Cloud Firestore w PWA

cloud firestore firebase

Cloud Firestore, to baza danych do tworzenia aplikacji mobilnych, internetowych i serwerowych od Firebase. Dziś klika słów na ten temat, gdyż używam tego rozwiązania do serwowania danych z Arduino na PWA zainstalowane na smartfonie.

Dane z Arduino przechodzą przez serwer PHP i to właśnie on wysyła dane do Firestore a ten pcha dane na mój telefon. Arduino co 30 minut wysyła dane na serwer, gdzie zapisuję je do bazy danych i jednocześnie aktualizuję bazę Firestore o konkretne dane i dalej to Firestore pushuje dane do PWA.

Czym jest PWA?

Należy zacząć od stworzenia PWA, o której pokrótce … Aplikacje PWA są uniwersalne i można je uruchomić wszędzie tam, gdzie jest dostępna przeglądarka internetowa. PWA to jednak niejedyna możliwość tworzenia aplikacji na wszystkie dostępne platformy. Bazuje ona na pilku manifest.json oraz service-worker.js, które definiują kształt i formę PWA. Oba pe pliki należy zlinkować z szblonem strony – manifest.json w sekcji <head> a service-worker jako <script>. Przykłady plików poniżej:

manifest.json

{
    "name": "Pan z Pogodna",
    "short_name": "Pan z Pogodna",
    "lang": "pl-PL",
    "start_url": "./pwa",
    "display": "standalone",
    "theme_color": "#171916",
    "background_color": "#171916",
    "icons": [
        {
          "src": "./assets/img/icons/icon-72x72.png",
          "sizes": "72x72",
          "type": "image/png"
        },
        {
          "src": "./assets/img/icons/icon-96x96.png",
          "sizes": "96x96",
          "type": "image/png"
        },
        {
          "src": "./assets/img/icons/icon-128x128.png",
          "sizes": "128x128",
          "type": "image/png"
        },
        {
          "src": "./assets/img/icons/icon-144x144.png",
          "sizes": "144x144",
          "type": "image/png"
        },
        {
          "src": "./assets/img/icons/icon-152x152.png",
          "sizes": "152x152",
          "type": "image/png"
        },
        {
          "src": "./assets/img/icons/icon-192x192.png",
          "sizes": "192x192",
          "type": "image/png"
        },
        {
          "src": "./assets/img/icons/icon-384x384.png",
          "sizes": "384x384",
          "type": "image/png"
        },
        {
          "src": "./assets/img/icons/icon-512x512.png",
          "sizes": "512x512",
          "type": "image/png"
        }
      ],
    "prefer_related_applications": true
}

service worker.js

const staticCacheName = 'site-statics-v3';
const dynamicCache = 'site-dynamic-v2';
const assets=[
    '/',
    '/index.php',
    '/assets/global/plugins/font-awesome/css/font-awesome.min.css',
];

self.addEventListener('install', evt => {
    console.log('sw has been installed');
    evt.waitUntil(
        caches.open(staticCacheName).then(cache => {
            console.log("caching assets");
            //cache.add()
            cache.addAll(assets);
        })
    )
});

self.addEventListener('activate', evt => {
    console.log('sw has been activated');
    evt.waitUntil(
        caches.keys().then(keys =>{
            //console.log(keys);
            return Promise.all(keys
                .filter (key => key !== staticCacheName)
                .map(key => caches.delete(key))
              )
        })
    );
});

//fetch event
self.addEventListener('fetch', evt =>{
    console.log('fetch event', evt);

    if(evt.request.url.indexOf('firestore.googleapis.com') === -1){
        evt.respondWith(
            caches.match(evt.reguest).then(cacheRes => {
                return cacheRes || fetch(evt.request).then(fetchRes => {
                    return caches.open(dynamicCache).then (cache => {
                        cache.put(evt.request.url, fetchRes.clone());
                        return fetchRes;
                    })
                });
            })
        )
    }
});

Nie będę się rozwodzić nad tym, co wyżej wypisany service-worker dokładnie robi – generalnie się inicjalizuje, cachuje assety i panuje nad requestami lecącymi z firestore.googleapis.com i to powinno wystarczyć. Warto zaznaczyć, że service-worker zadziała i wystartuje tylko w domenach z certyfikatem ssl, czyli zaczynających się od https . Trzeba zarejestrować naszego service-workera, którego dodaliśmy do strony. Można to zrobić tak:

<script>
    if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('/service-worker.js')
            .then((reg) => console.log('service worker registered !', reg))
            .catch((err) => console.log('error', err));
    }
</script>

Uruchomoiny servie-worker to warunek konieczny do działania pushowania danych na stronę, upewnij się, że w konsoli zobaczysz service worker registered ! w swojej konsoli tylko poprzez https.

Konto Firestore

Aby rozpocząć przygodę z Firestore nalezy założyć konto (jest to usługa googlowa) na firebase.google.com i utworzyć nowy projekt, podać jego nazwę itp a następnie stworzyć aplikację – w tym przypadku aplikację sieciową. Podać jej nazwę i tym podobne i wkleić uzyskany kod na swoją stronę.

<!-- The core Firebase JS SDK is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/7.21.0/firebase-app.js"></script>

<!-- TODO: Add SDKs for Firebase products that you want to use
     https://firebase.google.com/docs/web/setup#available-libraries -->
<script src="https://www.gstatic.com/firebasejs/7.21.0/firebase-analytics.js"></script>

<script>
  // Your web app's Firebase configuration
  // For Firebase JS SDK v7.20.0 and later, measurementId is optional
  var firebaseConfig = {
    apiKey: "xxx-xxxx",
    authDomain: "xxxxx-xxx.firebaseapp.com",
    databaseURL: "https://xxxxxx-46905.firebaseio.com",
    projectId: "xxxxxx-xxxxxx",
    storageBucket: "xxxx-xxxx.appspot.com",
    messagingSenderId: "xxxxx",
    appId: "1:xxxxxx:web:xxxxxa",
    measurementId: "G-xxxxx"
  };
  // Initialize Firebase
  firebase.initializeApp(firebaseConfig);
  firebase.analytics();
</script>

Kolejnym krokiem jest stworzenie bazy danych Cloud Firestore. Do tego trzeba z menu głównego z lewej strony wybrać Cloud Firestore i utworzyć nowa bazę danych. I już prawie koniec…. Stwórz kolekcję, dokument i dodaj do niej pola, które chcesz aktualizować i pobierać w dalszej części swojej aplikacji. W dalszej części postaram się zaprezentować jak z poziomu php aktualizować pola poprzez rest api i jak pobierać dane przez javascript. Póki co zobacz, jak wygląda przykładowa baza danych

Aktualizacja bazy przez REST API

Aby aktualizować rekordy poprzez PHP stwórz na serwerze plik php który będziesz wywoływać i z niego będzie wysyłać dane. Do tego uzyje CURLa, który prosto zaprezentuje udane zapytanie, oto przykład:

$ch = curl_init("https://firestore.googleapis.com/v1/projects/<projectId>/databases/(default)/documents/pzp/sensors");

    $json='{
        "fields":
            {"brightness":{"stringValue": "'.$brightness.'"}},
        "fields":
            {"humBasement":{"stringValue": "'.$humidityPiwnica.'"}},
        "fields":
            {"humGarage":{"stringValue": "'.$humidityGaraz.'"}},
        "fields":
            {"humOutside":{"stringValue": "'.$humidityOutside.'"}},
        "fields":
            {"humPralnia":{"stringValue": "'.$humidityPralnia.'"}},
        "fields":
            {"ip":{"stringValue": "'.$ip.'"}},
        "fields":
            {"pressure":{"stringValue": "'.$pressure.'"}},
        "fields":
            {"tempBasement":{"stringValue": "'.$temperaturaPiwnica.'"}},
        "fields":
            {"tempGarage":{"stringValue": "'.$temperaturaGaraz.'"}},
        "fields":
            {"tempOutside":{"stringValue": "'.$temperaturaOutside.'"}},
        "fields":
            {"tempPralnia":{"stringValue": "'.$temperaturaPralnia.'"}},
        "fields":
            {"lastSynchro":{"stringValue": "'.$nowDate.'"}}
    }';

    $headers = array();
    $headers[] = 'Content-Type: application/json';
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH");
    curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
    curl_setopt($ch, CURLOPT_HTTPHEADER,$headers);
    $response = curl_exec($ch);
    curl_close($ch);

I to tyle… zwróć uwage, że w linku $ch musisz ustawić projectId z kawałka kodu jaki przekeliłeś na stronę oraz ścieżkę do swoich pól w bazie – w moim przypadku /pzp/sensors. Nastepnie w $json tworzysz pola z wartościami jakie chcesz zapisać. Każde wysłanie curla spowoduje nadpisanie poprzednich wartości.

Odczyt danych z Cloud Firestore

Aby przesłać, pushować dane z Cloud Firestore do swojej strony, aplikacji czy PWA z wykorzystaniem javascriptu należy zainicjować zmienną db, najlepiej zaraz po tym jak zainicjalizowany został firebase – czyli w miejscu gdzie przekopiowałeś kod na swoją stronę.

firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
firebase.analytics();

Gdy już jest stworzona zmienna db, pozostaje się do nie odwołać dalej w javascripcie, na przykład tak:

db.enablePersistence()
.catch(err => {
	if(err.code == 'failed-precondition'){
		console.log('faled');
	}else if(err.code == 'unimplemented'){
		console.log('faled');
	}
});


db.collection("pzp").doc("sensors")
.onSnapshot(function(doc) {
	//  console.log(doc);
	console.log(doc.data().tempOutside);
	console.log(doc.data().humOutside);
	console.log(doc.data().pressure);
	console.log(doc.data().tempBasement);
	console.log(doc.data().humBasement);
	console.log(doc.data().tempPralnia);
	console.log(doc.data().humPralnia);
	console.log(doc.data().tempGarage);
	console.log(doc.data().humGarage);
	console.log(doc.data().lastSynchro);

});

To tyle. Zasada działania jest taka, że ilekroć dane w Firestore zostaną zmienione czy to „z palca” czy poprzez wspomnianego CURL’a czy jeszcze inaczej, tyle razy zaktualizowane dane trafią do twojej aplikacji automatycznie, co można sprawdzić podglądając konsolę.


Opublikowano: 21 września, 2020 przez Pan z Pogodna

Leave a Reply