Step 16 - Save push subscriptions in a DB
February 13, 2020 · View on GitHub
You have already experienced that copying and pasting the push subscription every time for a client is not optimal.
We need to keep our users' push subscriptions in a database and query them to send push notifications to all our users.
In this step, we're going to create a new DB on Firebase to collect push subscirption data. After that, we will adjust our sendPush script to query all the subscriptions first and then send a notification to all subscription.
By doing so, we will be getting rid of manual copy/paste procedure of subscription object that we were experimenting with in previous step.
Create a new database
Navigate to Database page using side navigation. Click on Create database button.
Choose a security rule to start with
Choose Start in test mode for testing purposes.
Note that, you need to adjust your database's security rules before going to production environment. Read more about Structuring Cloud Firestore Security Rules.
Create a new collection
Click on Start collection on your Firebase console. Set the name pushUsers for the Collection ID.
Initialize Firebase configuration files
Now that we have set up the Firestore DB on console, we can introduce the project files to our app.
Run npx firebase init on your console in your project root and select Firestore from the list of services.
Proceed with the default config. You should have 2 new files:
firestore.indexes.json
firestore.rules
Now you can configure your DB over those files and deploy the new settings by running npx firebase deploy.
Save each push subscription to the DB
The easiest way to interact with a Firebase service within Angular project is using @angular/fire package.
Install @angular/fire
Execute the following command to install @angular/fire:
npm install firebase @angular/fire --save
Configure @angular/fire
@angular/fire requires a specific project configuration as it's described on it's Install and Setup docs.
- Navigate to
Firebase Consoleto copy your project id.
- Open
environment.tsandenvironment.prod.tsfiles and add the following config to the env object, pasting your firebase project id:
firebase: {
projectId: '⚠️ PUT YOUT FIREBASE PROJECT ID HERE ⚠️'
}
- Open
app.module.tsfile and import/add 2 new modules from@angular/firepackage:
import { AngularFirestoreModule } from '@angular/fire/firestore';
import { AngularFireModule } from '@angular/fire';
import { environment } from '../environments/environment';
@NgModule({
imports: [
AngularFireModule.initializeApp(environment.firebase),
AngularFirestoreModule,
],
})
Save push subscriptions to the DB
We're going to store push subscriptions in the pushUsers collection we created in our DB.
Open app.component.ts file and add the following import statements and interface declaration:
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore';
interface PushUser {
subscription: {
endpoint: string;
expirationTime: number | null;
keys: {
auth: string;
p256dh: string;
}
};
}
Create a collection reference
Within app.component.ts file, inject the AngularFirestore service and create a new collection reference:
pushUsersCollection: AngularFirestoreCollection<PushUser>;
constructor(
private db: AngularFirestore,
) {
this.pushUsersCollection = db.collection<PushUser>('pushUsers');
}
Over the collection reference we can query the documents, save new documents and do more. Let's save our subscription as a new document using it.
Save subscription as a new document
Add the new method addPushUser below to your app.component.ts file:
addPushUser(pushUser: PushUser) {
return this.pushUsersCollection.doc(pushUser.subscription.keys.auth).set(pushUser, { merge: true });
}
Update subscribeToWebPush() method with the content below to call the new addPushUser method:
subscribeToWebPush() {
if ('Notification' in window && Notification.permission === 'granted') {
this.swPush.requestSubscription({
serverPublicKey: `⚠️ PUT YOUR FIREBASE_WEB_PUSH_PUBLIC_VAPID_KEY HERE ⚠️`,
}).then((sub) => {
const subscription = JSON.parse(JSON.stringify(sub));
console.log('subscribeToWebPush successful');
console.log(subscription);
this.addPushUser({ subscription });
}).catch((err) => {
console.log('subscribeToWebPush error', err);
});
}
}
Normally, adding a new item as a document to our collection is as easy as calling this.pushUsersCollection.add(pushUser). However, this creates a new document with a different doc id every time, with the same subscription data.
By using pushUser.subscription.keys.auth variable as a document id and providing merge: true option, we make sure we don't have duplicate entries for the same subscription.
Test the db entries
- Build and serve your app by running
npm run build:serve:https. - Navigate to https://localhost on your desktop and to https://10.0.2.2 on your emulated device.
- Open the database UI on your firebase console.
- You should have 2 different documents in your collection.
Query the DB and send batch notifications to all subscribers
We still need to update our sendPush.js script to send the push notifications using the list of documents stored in our Firestore DB.
Add new environment variables
Open .env file and add the following environment variables:
FIREBASE_PROJECT_ID="⚠️ PUT YOUT FIREBASE PROJECT ID HERE ⚠️"
FIREBASE_FIRESTORE_COLLECTION_NAME="pushUsers"
Update the sendPush script
We're going to query all the documents from our collection to retrieve a list of subscriptions.
Finally, we will send a message to each subscription using webpush.
Open sendPush.js file and replace the file contents with the code below:
const webpush = require('web-push');
const firebase = require('firebase/app');
require('firebase/firestore');
(async () => {
webpush.setGCMAPIKey(process.env.FIREBASE_SERVER_API_KEY);
webpush.setVapidDetails(
`mailto:${process.env.EMAIL_FOR_SUBJECT}`,
process.env.FIREBASE_WEB_PUSH_PUBLIC_VAPID_KEY,
process.env.FIREBASE_WEB_PUSH_PRIVATE_VAPID_KEY
);
const notificationPayload = {
notification: {
title: 'Session is about the start 🏃♀️',
body: '"Community Interaction" by Gino Giraffe is starting in Hall 3.',
icon: 'assets/pwa/manifest-icon-192.png',
vibrate: [100, 50, 100],
data: {
dateOfArrival: Date.now(),
},
actions: [
{
action: 'session:10',
title: 'Session info 👉',
},
{
action: 'speaker:6',
title: 'Speaker info 🗣',
}
]
}
};
const firebaseConfig = {
projectId: process.env.FIREBASE_PROJECT_ID,
};
firebase.initializeApp(firebaseConfig);
const pushUsersRef = firebase.firestore().collection(process.env.FIREBASE_FIRESTORE_COLLECTION_NAME);
const pushUsersSnapshot = await pushUsersRef.get();
const pushUsers = pushUsersSnapshot.docs;
let successCount = 0;
for (let i = 0; i < pushUsers.length; i++) {
try {
await webpush.sendNotification(pushUsers[i].data().subscription, JSON.stringify(notificationPayload));
successCount++;
} catch (error) {
if (error.body.includes('push subscription has unsubscribed')) {
await pushUsersRef.doc(pushUsers[i].data().subscription.keys.auth).delete();
} else {
console.log(e);
}
}
}
console.log(`Message is sent to ${successCount} of ${pushUsersSnapshot.size} subscribers`);
if (pushUsersSnapshot.size - successCount > 0) {
console.log(`Removed ${pushUsersSnapshot.size - successCount} of expired subscriptions`);
}
process.exit(0);
})();
Test sending batch push messages
Open your app on as much as device as you can. Execute npm run push.
You should be getting the push notification on each of your test devices.
Good to go 🎯
Now you can continue to Step 17 -> Use an API from project Fugu.