diff --git a/public/firebase-messaging-sw.js b/public/firebase-messaging-sw.js
new file mode 100644
index 000000000..6d0bd55e6
--- /dev/null
+++ b/public/firebase-messaging-sw.js
@@ -0,0 +1,82 @@
+/* eslint-disable */
+// firebase-messaging-sw.js
+importScripts(
+ "https://www.gstatic.com/firebasejs/10.7.1/firebase-app-compat.js",
+);
+importScripts(
+ "https://www.gstatic.com/firebasejs/10.7.1/firebase-messaging-compat.js",
+);
+
+const ENV = {
+ LOCAL: "http://localhost:3000",
+ DEV: "https://web-dev.common.io",
+ STAGE: "https://web-staging.common.io",
+ PRODUCTION: "https://common.io",
+};
+
+const FIREBASE_CONFIG_ENV = {
+ DEV: {
+ apiKey: "AIzaSyDbTFuksgOkIVWDiFe_HG7-BE8X6Dwsg-0",
+ authDomain: "common-dev-34b09.firebaseapp.com",
+ databaseURL: "https://common-dev-34b09.firebaseio.com",
+ projectId: "common-dev-34b09",
+ storageBucket: "common-dev-34b09.appspot.com",
+ messagingSenderId: "870639147922",
+ appId: "1:870639147922:web:9ee954bb1dd52e25cb7f4b",
+ },
+ STAGE: {
+ apiKey: "AIzaSyBASCWJMV64mZJObeFEitLmdUC1HqmtjJk",
+ authDomain: "common-staging-1d426.firebaseapp.com",
+ databaseURL: "https://common-staging-1d426.firebaseio.com",
+ projectId: "common-staging-1d426",
+ storageBucket: "common-staging-1d426.appspot.com",
+ messagingSenderId: "701579202562",
+ appId: "1:701579202562:web:5729d8a875f98f6709571b",
+ },
+ PRODUCTION: {
+ apiKey: "AIzaSyAlYrKLd6KNKVkhmNEMKfb0cWHSWicCBOY",
+ authDomain: "common-production-67641.firebaseapp.com",
+ databaseURL: "https://common-production-67641.firebaseio.com",
+ projectId: "common-production-67641",
+ storageBucket: "common-production-67641.appspot.com",
+ messagingSenderId: "461029494046",
+ appId: "1:461029494046:web:4e2e4afbbeb7b487b48d0f",
+ },
+};
+
+let firebaseConfig = {};
+
+switch (location.origin) {
+ case ENV.LOCAL:
+ case ENV.DEV: {
+ firebaseConfig = FIREBASE_CONFIG_ENV.DEV;
+ break;
+ }
+ case ENV.STAGE: {
+ firebaseConfig = FIREBASE_CONFIG_ENV.STAGE;
+ break;
+ }
+ case ENV.PRODUCTION: {
+ firebaseConfig = FIREBASE_CONFIG_ENV.PRODUCTION;
+ break;
+ }
+ default: {
+ firebaseConfig = FIREBASE_CONFIG_ENV.DEV;
+ break;
+ }
+}
+
+firebase.initializeApp(firebaseConfig);
+
+const messaging = firebase.messaging();
+
+messaging.onBackgroundMessage((payload) => {
+ const notificationTitle = payload.notification.title;
+ const notificationOptions = {
+ body: payload.notification.body,
+ data: payload.data,
+ icon: "/logo.png",
+ };
+
+ self.registration.showNotification(notificationTitle, notificationOptions);
+});
diff --git a/public/logo.png b/public/logo.png
new file mode 100644
index 000000000..94e4735b4
Binary files /dev/null and b/public/logo.png differ
diff --git a/src/config.tsx b/src/config.tsx
index be0bab8e2..5975af4f0 100644
--- a/src/config.tsx
+++ b/src/config.tsx
@@ -27,6 +27,8 @@ export const local: Configuration = {
deadSeaCommonId: "958dca85-7bc1-4714-95bd-1fc6343f0654",
parentsForClimateCommonId: "958dca85-7bc1-4714-95bd-1fc6343f0654",
saadiaCommonId: "958dca85-7bc1-4714-95bd-1fc6343f0654",
+ vapidKey:
+ "BHVFyNetSC6oA2uFejnUFuDcSUYcas2R5lwW80z6gZc6zODp7rRdh2t8bht3LygJWjyI1toV165EYgdZqxCS_Y4",
};
const dev: Configuration = {
@@ -44,6 +46,8 @@ const dev: Configuration = {
deadSeaCommonId: "958dca85-7bc1-4714-95bd-1fc6343f0654",
parentsForClimateCommonId: "958dca85-7bc1-4714-95bd-1fc6343f0654",
saadiaCommonId: "958dca85-7bc1-4714-95bd-1fc6343f0654",
+ vapidKey:
+ "BHVFyNetSC6oA2uFejnUFuDcSUYcas2R5lwW80z6gZc6zODp7rRdh2t8bht3LygJWjyI1toV165EYgdZqxCS_Y4",
};
const stage: Configuration = {
@@ -62,6 +66,8 @@ const stage: Configuration = {
deadSeaCommonId: "a55a1e9b-104a-4866-9f4f-3e017bbae281",
parentsForClimateCommonId: "a55a1e9b-104a-4866-9f4f-3e017bbae281",
saadiaCommonId: "a55a1e9b-104a-4866-9f4f-3e017bbae281",
+ vapidKey:
+ "BBvr8z8QaPSJJfIRxmjBrq5Vs49BY95uZK_6QFyR7gKWgwrs5toDy-hvwWEtk-rbkVHBgOu9l2orK45u1n--9M0",
};
const production: Configuration = {
@@ -80,6 +86,8 @@ const production: Configuration = {
deadSeaCommonId: "6cfbfae6-2e5c-4b3b-ba70-e8fd871f48e2",
parentsForClimateCommonId: "04ac2ec2-5cb2-4ab9-ae3f-5f223f482768",
saadiaCommonId: "7c8c8996-b678-44df-9a57-e291431eb00f",
+ vapidKey:
+ "BKJ324iR-B5SoDG42bMrC_Q_poAv7BO-Z3AuMh5Grrg6TxO1QnN6mgzt2KyFFax0JSuuUhUKP-OrcTUPfboVqns",
};
const config: ConfigurationObject = {
diff --git a/src/pages/App/App.tsx b/src/pages/App/App.tsx
index 4ffe2e826..5e0c24206 100644
--- a/src/pages/App/App.tsx
+++ b/src/pages/App/App.tsx
@@ -13,6 +13,7 @@ import {
ThemeHandler,
UserNotificationsAmountHandler,
WebViewLoginHandler,
+ NotificationsHandler,
} from "./handlers";
import { Router } from "./router";
@@ -34,6 +35,7 @@ const App = () => {
+
diff --git a/src/pages/App/handlers/NotificationsHandler/NotificationsHandler.tsx b/src/pages/App/handlers/NotificationsHandler/NotificationsHandler.tsx
new file mode 100644
index 000000000..de144355b
--- /dev/null
+++ b/src/pages/App/handlers/NotificationsHandler/NotificationsHandler.tsx
@@ -0,0 +1,48 @@
+import { FC, useEffect, useState } from "react";
+import { useSelector } from "react-redux";
+import { selectUser } from "@/pages/Auth/store/selectors";
+import { NotificationService } from "@/services";
+
+const NotificationsHandler: FC = () => {
+ const user = useSelector(selectUser());
+ const userId = user?.uid;
+ const [isRegistered, setIsRegistered] = useState(false);
+
+ useEffect(() => {
+ if ("serviceWorker" in navigator) {
+ navigator.serviceWorker
+ .register("/firebase-messaging-sw.js")
+ .then((registration) => {
+ setIsRegistered(true);
+ return registration;
+ })
+ .catch((err) => {
+ console.log("ServiceWorker registration failed: ", err);
+ });
+ }
+ }, []);
+
+ useEffect(() => {
+ if (!userId && !isRegistered) {
+ return;
+ }
+
+ let unsubscribeOnMessage;
+ (async () => {
+ const hasPermissions = await NotificationService.requestPermissions();
+ if (hasPermissions) {
+ await NotificationService.saveFCMToken();
+
+ unsubscribeOnMessage = NotificationService.onForegroundMessage();
+ }
+ })();
+
+ return () => {
+ unsubscribeOnMessage && unsubscribeOnMessage();
+ };
+ }, [userId, isRegistered]);
+
+ return null;
+};
+
+export default NotificationsHandler;
diff --git a/src/pages/App/handlers/NotificationsHandler/index.ts b/src/pages/App/handlers/NotificationsHandler/index.ts
new file mode 100644
index 000000000..c7c53200f
--- /dev/null
+++ b/src/pages/App/handlers/NotificationsHandler/index.ts
@@ -0,0 +1 @@
+export { default as NotificationsHandler } from "./NotificationsHandler";
\ No newline at end of file
diff --git a/src/pages/App/handlers/index.ts b/src/pages/App/handlers/index.ts
index 712e7a4d4..37e8eb0fb 100644
--- a/src/pages/App/handlers/index.ts
+++ b/src/pages/App/handlers/index.ts
@@ -3,3 +3,4 @@ export * from "./TextDirectionHandler";
export * from "./UserNotificationsAmountHandler";
export * from "./WebViewLoginHandler";
export * from "./ThemeHandler";
+export * from "./NotificationsHandler";
diff --git a/src/services/Notification.ts b/src/services/Notification.ts
new file mode 100644
index 000000000..25e7597b3
--- /dev/null
+++ b/src/services/Notification.ts
@@ -0,0 +1,73 @@
+import firebase from "@/shared/utils/firebase";
+import firebaseConfig from "@/config";
+import Api from "./Api";
+
+enum NOTIFICATIONS_PERMISSIONS {
+ DEFAULT = "default",
+ DENIED = "denied",
+ GRANTED = "granted"
+}
+
+
+class NotificationService {
+ private endpoints: {
+ setFCMToken: string;
+ };
+
+ constructor() {
+ this.endpoints = {
+ setFCMToken: '/users/auth/google/set-fcm-token',
+ };
+ }
+
+ public requestPermissions = async (): Promise => {
+ try {
+ if(Notification.permission === NOTIFICATIONS_PERMISSIONS.GRANTED) {
+ return true;
+ }
+ const permission = await Notification.requestPermission();
+ if (permission === NOTIFICATIONS_PERMISSIONS.GRANTED) {
+ return true;
+ } else {
+ return false;
+ }
+ } catch (err) {
+ return false;
+ }
+ }
+
+ public saveFCMToken = async (): Promise => {
+ try {
+ const token = await firebase.messaging().getToken({ vapidKey: firebaseConfig.vapidKey });
+ if (token) {
+
+ await Api.post(
+ this.endpoints.setFCMToken,
+ {
+ token,
+ }
+ );
+ }
+ } catch (error) {
+ console.error("An error occurred while retrieving token. ", error);
+ }
+ }
+
+ public onForegroundMessage = () => {
+ const unsubscribe = firebase.messaging().onMessage((payload) => {
+
+ const { title, body } = payload.notification;
+ if (Notification.permission === 'granted') {
+ new Notification(title, {
+ body,
+ data: payload?.data,
+ icon: "/logo.png",
+ });
+ }
+ });
+
+ return unsubscribe;
+ }
+}
+
+export default new NotificationService();
diff --git a/src/services/index.ts b/src/services/index.ts
index 03d9bf706..bf6d10f1e 100644
--- a/src/services/index.ts
+++ b/src/services/index.ts
@@ -25,3 +25,4 @@ export {
} from "./DiscussionMessage";
export { default as NotionService } from "./Notion";
export { default as FeatureFlagService } from "./FeatureFlag";
+export { default as NotificationService } from "./Notification";
diff --git a/src/shared/interfaces/Configuration.tsx b/src/shared/interfaces/Configuration.tsx
index 94c71bcfc..73f6e96ef 100644
--- a/src/shared/interfaces/Configuration.tsx
+++ b/src/shared/interfaces/Configuration.tsx
@@ -15,6 +15,7 @@ export interface Configuration {
deadSeaCommonId: string;
parentsForClimateCommonId: string;
saadiaCommonId: string;
+ vapidKey: string;
}
export type ConfigurationObject = Record;
diff --git a/src/shared/utils/firebase.tsx b/src/shared/utils/firebase.tsx
index 3db4544c9..a7f790cf0 100644
--- a/src/shared/utils/firebase.tsx
+++ b/src/shared/utils/firebase.tsx
@@ -1,6 +1,7 @@
import firebase from "firebase/compat/app";
import "firebase/compat/auth";
import "firebase/compat/firestore";
+import "firebase/compat/messaging";
import "firebase/compat/performance";
import "firebase/compat/storage";
import { getPerformance } from "firebase/performance";
diff --git a/src/shared/utils/tests/mockConfig.ts b/src/shared/utils/tests/mockConfig.ts
index 502ef3bcf..8f9410f5a 100644
--- a/src/shared/utils/tests/mockConfig.ts
+++ b/src/shared/utils/tests/mockConfig.ts
@@ -18,5 +18,6 @@ jest.mock(
deadSeaCommonId: "958dca85-7bc1-4714-95bd-1fc6343f0654",
parentsForClimateCommonId: "958dca85-7bc1-4714-95bd-1fc6343f0654",
saadiaCommonId: "958dca85-7bc1-4714-95bd-1fc6343f0654",
+ vapidKey: "VAPID_KEY",
})
);