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.
Navigate to Database
page using side navigation. Click on Create database
button.
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.
Click on Start collection
on your Firebase console. Set the name pushUsers
for the Collection ID.
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
.
The easiest way to interact with a Firebase service within Angular project is using @angular/fire package.
Execute the following command to install @angular/fire:
npm install firebase @angular/fire --save
@angular/fire
requires a specific project configuration as it's described on it's Install and Setup docs.
- Navigate to
Firebase Console
to copy your project id.
- Open
environment.ts
andenvironment.prod.ts
files 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.ts
file and import/add 2 new modules from@angular/fire
package:
import { AngularFirestoreModule } from '@angular/fire/firestore';
import { AngularFireModule } from '@angular/fire';
import { environment } from '../environments/environment';
@NgModule({
imports: [
AngularFireModule.initializeApp(environment.firebase),
AngularFirestoreModule,
],
})
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;
}
};
}
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.
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.
- 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.
We still need to update our sendPush.js
script to send the push notifications using the list of documents stored in our Firestore DB.
Open .env
file and add the following environment variables:
FIREBASE_PROJECT_ID="⚠️ PUT YOUT FIREBASE PROJECT ID HERE ⚠️"
FIREBASE_FIRESTORE_COLLECTION_NAME="pushUsers"
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);
})();
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.
Now you can continue to Step 17 -> Use an API from project Fugu.