diff --git a/angular.json b/angular.json
index 211a8b4..699fef8 100644
--- a/angular.json
+++ b/angular.json
@@ -29,7 +29,8 @@
"assets": [
"src/favicon.ico",
"src/assets",
- "src/manifest.webmanifest"
+ "src/manifest.webmanifest",
+ "src/firebase-messaging-sw.js"
],
"styles": [
"src/styles.scss"
diff --git a/functions/src/index.ts b/functions/src/index.ts
index 283db56..97c44a6 100644
--- a/functions/src/index.ts
+++ b/functions/src/index.ts
@@ -1,9 +1,41 @@
-import * as functions from "firebase-functions";
-
-// // Start writing Firebase Functions
-// // https://firebase.google.com/docs/functions/typescript
-//
-export const helloWorld = functions.https.onRequest((request, response) => {
- functions.logger.info("Hello logs!", {structuredData: true});
- response.send("Hello from Firebase!");
+import {pubsub} from "firebase-functions";
+import * as admin from "firebase-admin";
+import {MessagingPayload} from "firebase-admin/lib/messaging/messaging-api";
+
+const app = admin.initializeApp();
+const firestore = app.firestore();
+const messaging = app.messaging();
+
+export const sendPush = pubsub.schedule("every 1 minutes").onRun(async () => {
+ const users = await firestore.collection("users").listDocuments();
+ users.forEach(async (user) => {
+ const plants = await user.collection("plants").get();
+ plants.forEach(async (plant) => {
+ const plantData = plant.data();
+
+ const timeFormatOptions: Intl.DateTimeFormatOptions = {
+ timeZone: plantData.timezone,
+ timeStyle: "short",
+ };
+ // eslint-disable-next-line
+ const timeInPlantTimezone = new Intl.DateTimeFormat("PL", timeFormatOptions).format();
+
+ if (timeInPlantTimezone === plantData.waterTime) {
+ const payload: MessagingPayload = {
+ notification: {
+ title: `It's time to water ${plantData.name}`,
+ body: plantData.description,
+ image: plantData.imageUrl ? plantData.imageUrl : "https://watering-reminder.web.app/assets/images/default_plant_image.png",
+ icon: "https://watering-reminder.web.app/assets/icons/icon-144x144.png",
+ },
+ };
+
+ const fcmTokens = await user.collection("fcmTokens").get();
+ fcmTokens.forEach(async (token) => {
+ const tokenData = token.data() as { fcmToken: string };
+ await messaging.sendToDevice(tokenData.fcmToken, payload);
+ });
+ }
+ });
+ });
});
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index ecb3ac3..0c6cebe 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -60,18 +60,18 @@ import { connectAuthEmulator } from '@firebase/auth'
provideMessaging(() => getMessaging()),
provideAuth(() => {
const auth = getAuth()
- connectAuthEmulator(auth, 'http://127.0.0.1:9099', { disableWarnings: true })
+ if(!environment.production) connectAuthEmulator(auth, 'http://127.0.0.1:9099', { disableWarnings: true })
return auth
}),
provideFirestore(() => {
const firestore = getFirestore()
- connectFirestoreEmulator(firestore, 'localhost', 8080)
+ if(!environment.production) connectFirestoreEmulator(firestore, 'localhost', 8080)
enableIndexedDbPersistence(firestore)
return firestore
}),
provideStorage(() => {
const storage = getStorage()
- connectStorageEmulator(storage, 'localhost', 9199)
+ if(!environment.production) connectStorageEmulator(storage, 'localhost', 9199)
return storage
}),
],
diff --git a/src/app/pages/dashboard/dashboard.component.html b/src/app/pages/dashboard/dashboard.component.html
index 2298edd..3933618 100644
--- a/src/app/pages/dashboard/dashboard.component.html
+++ b/src/app/pages/dashboard/dashboard.component.html
@@ -1,23 +1,31 @@
-
dashboard works!
-
+
-
-
-
- Loading
+
+ You have to allow push notifications to use this app.
-
-
+
+ dashboard works!
+
+
+
-
- You have no plants.
-
+
+ Loading
-
-
-
-
+
+
+
+
+ You have no plants.
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/pages/dashboard/dashboard.component.ts b/src/app/pages/dashboard/dashboard.component.ts
index 4cf46d7..103ea8e 100644
--- a/src/app/pages/dashboard/dashboard.component.ts
+++ b/src/app/pages/dashboard/dashboard.component.ts
@@ -1,6 +1,8 @@
import { Component } from '@angular/core'
+import { Messaging, getToken, } from '@angular/fire/messaging'
import { Router } from '@angular/router'
import { AuthService } from 'src/app/services/auth/auth.service'
+import { NotificationsService } from 'src/app/services/notifiications/notifications.service'
import { PlantsService } from 'src/app/services/plants/plants.service'
@Component({
@@ -10,12 +12,18 @@ import { PlantsService } from 'src/app/services/plants/plants.service'
})
export class DashboardComponent {
isLoading = true
+
- constructor(public auth: AuthService, public router: Router, public plantsService: PlantsService) {
+ constructor(public auth: AuthService, public router: Router, public plantsService: PlantsService, public notificationsService: NotificationsService) {
this.plantsService.plants$.subscribe(() => this.isLoading = false)
+
+ if(!this.notificationsService.areNotificationsAllowed) {
+ this.notificationsService.registerToken()
+ }
}
async logout() {
+ await this.notificationsService.unregisterToken()
await this.auth.logout()
await this.router.navigate(['/login'])
}
diff --git a/src/app/services/notifiications/notifications.service.spec.ts b/src/app/services/notifiications/notifications.service.spec.ts
new file mode 100644
index 0000000..c939c4c
--- /dev/null
+++ b/src/app/services/notifiications/notifications.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { NotificationsService } from './notifications.service';
+
+describe('NotificationsService', () => {
+ let service: NotificationsService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(NotificationsService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/src/app/services/notifiications/notifications.service.ts b/src/app/services/notifiications/notifications.service.ts
new file mode 100644
index 0000000..8c63505
--- /dev/null
+++ b/src/app/services/notifiications/notifications.service.ts
@@ -0,0 +1,41 @@
+import { Injectable } from '@angular/core';
+import { addDoc, collection, deleteDoc, doc, Firestore, setDoc } from '@angular/fire/firestore'
+import { Messaging } from '@angular/fire/messaging'
+import { getToken } from '@firebase/messaging'
+import { lastValueFrom, take } from 'rxjs'
+import { AuthService } from '../auth/auth.service'
+
+@Injectable({
+ providedIn: 'root'
+})
+export class NotificationsService {
+ areNotificationsAllowed = Notification.permission === 'granted'
+
+ constructor(private messaging: Messaging, private auth: AuthService, private firestore: Firestore) { }
+
+ async registerToken() {
+ const user = await lastValueFrom(this.auth.user$.pipe(take(1)))
+
+ if (!user) {
+ throw new Error("User is not logged in")
+ }
+
+ const fcmToken = await getToken(this.messaging)
+ const d = doc(this.firestore, "users", user.uid, "fcmTokens", fcmToken)
+ await setDoc(d, { fcmToken })
+ this.areNotificationsAllowed = true
+ }
+
+ async unregisterToken() {
+ const user = await lastValueFrom(this.auth.user$.pipe(take(1)))
+
+ if (!user) {
+ throw new Error("User is not logged in")
+ }
+
+ const fcmToken = await getToken(this.messaging)
+ const d = doc(this.firestore, "users", user.uid, "fcmTokens", fcmToken)
+ await deleteDoc(d)
+ this.areNotificationsAllowed = false
+ }
+}
diff --git a/src/firebase-messaging-sw.js b/src/firebase-messaging-sw.js
new file mode 100644
index 0000000..7d000a9
--- /dev/null
+++ b/src/firebase-messaging-sw.js
@@ -0,0 +1,20 @@
+importScripts('https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js');
+importScripts('https://www.gstatic.com/firebasejs/8.10.0/firebase-messaging.js');
+
+// Initialize the Firebase app in the service worker by passing in
+// your app's Firebase config object.
+// https://firebase.google.com/docs/web/setup#config-object
+firebase.initializeApp({
+ apiKey: 'api-key',
+ authDomain: 'project-id.firebaseapp.com',
+ databaseURL: 'https://project-id.firebaseio.com',
+ projectId: 'project-id',
+ storageBucket: 'project-id.appspot.com',
+ messagingSenderId: 'sender-id',
+ appId: 'app-id',
+ measurementId: 'G-measurement-id',
+});
+
+// Retrieve an instance of Firebase Messaging so that it can handle background
+// messages.
+const messaging = firebase.messaging();
\ No newline at end of file