Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Notification Handling and Rename Firebase Service Worker Scope #497

Merged
merged 9 commits into from
Jan 23, 2025
114 changes: 50 additions & 64 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, Suspense } from 'react';
import React, { Suspense } from 'react';
import { Routes, Route, Outlet, useLocation } from 'react-router-dom';
// Import i18next and set up translations
import { I18nextProvider } from 'react-i18next';
Expand All @@ -7,7 +7,7 @@ import i18n from './i18n';
import { withSessionContext } from './context/SessionContext';

import FadeInContentTransition from './components/Transitions/FadeInContentTransition';
import HandlerNotification from './components/Notifications/HandlerNotification';
import NewCredentialNotification from './components/Notifications/NewCredentialNotification';
import Snowfalling from './components/ChristmasAnimation/Snowfalling';
import Spinner from './components/Shared/Spinner';

Expand All @@ -19,6 +19,8 @@ import { withUriHandler } from './UriHandler';
import { withCredentialParserContext } from './context/CredentialParserContext';
import { withOpenID4VPContext } from './context/OpenID4VPContext';
import { withOpenID4VCIContext } from './context/OpenID4VCIContext';
import useNewCredentialListener from './hooks/useNewCredentialListener';
import BackgroundNotificationClickHandler from './components/Notifications/BackgroundNotificationClickHandler';

const reactLazyWithNonDefaultExports = (load, ...names) => {
const nonDefaults = (names ?? []).map(name => {
Expand Down Expand Up @@ -88,70 +90,54 @@ const NotFound = lazyWithDelay(() => import('./pages/NotFound/NotFound'), 400);

function App() {
const location = useLocation();
useEffect(() => {
if (navigator?.serviceWorker) {
navigator.serviceWorker.addEventListener('message', handleMessage);
// Clean up the event listener when the component unmounts
return () => {
navigator.serviceWorker.removeEventListener('message', handleMessage);
};
}

}, []);

// Handle messages received from the service worker
const handleMessage = (event) => {
if (event.data.type === 'navigate') {
// Remove any parameters from the URL
const homeURL = window.location.origin + window.location.pathname;
// Redirect the current tab to the home URL
window.location.href = homeURL;
}
};
const { notification, clearNotification } = useNewCredentialListener();

return (
<I18nextProvider i18n={i18n}>
<Snowfalling />
<Suspense fallback={<Spinner />}>
<HandlerNotification />
<UpdateNotification />
<Routes>
<Route element={
<PrivateRoute>
<Layout>
<Suspense fallback={<Spinner size='small' />}>
<PrivateRoute.NotificationPermissionWarning />
<FadeInContentTransition appear reanimateKey={location.pathname}>
<Outlet />
</FadeInContentTransition>
</Suspense>
</Layout>
</PrivateRoute>
}>
<Route path="/settings" element={<Settings />} />
<Route path="/" element={<Home />} />
<Route path="/credential/:credentialId" element={<Credential />} />
<Route path="/credential/:credentialId/history" element={<CredentialHistory />} />
<Route path="/credential/:credentialId/details" element={<CredentialDetails />} />
<Route path="/history" element={<History />} />
<Route path="/history/:historyId" element={<HistoryDetail />} />
<Route path="/add" element={<AddCredentials />} />
<Route path="/send" element={<SendCredentials />} />
<Route path="/verification/result" element={<VerificationResult />} />
<Route path="/cb/*" element={<Home />} />
</Route>
<Route element={
<FadeInContentTransition reanimateKey={location.pathname}>
<Outlet />
</FadeInContentTransition>
}>
<Route path="/login" element={<Login />} />
<Route path="/login-state" element={<LoginState />} />
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
</Suspense>
</I18nextProvider>
<>
<BackgroundNotificationClickHandler />
<I18nextProvider i18n={i18n}>
<Snowfalling />
<Suspense fallback={<Spinner />}>
<NewCredentialNotification notification={notification} clearNotification={clearNotification} />
<UpdateNotification />
<Routes>
<Route element={
<PrivateRoute>
<Layout>
<Suspense fallback={<Spinner size='small' />}>
<PrivateRoute.NotificationPermissionWarning />
<FadeInContentTransition appear reanimateKey={location.pathname}>
<Outlet />
</FadeInContentTransition>
</Suspense>
</Layout>
</PrivateRoute>
}>
<Route path="/settings" element={<Settings />} />
<Route path="/" element={<Home />} />
<Route path="/credential/:credentialId" element={<Credential />} />
<Route path="/credential/:credentialId/history" element={<CredentialHistory />} />
<Route path="/credential/:credentialId/details" element={<CredentialDetails />} />
<Route path="/history" element={<History />} />
<Route path="/history/:historyId" element={<HistoryDetail />} />
<Route path="/add" element={<AddCredentials />} />
<Route path="/send" element={<SendCredentials />} />
<Route path="/verification/result" element={<VerificationResult />} />
<Route path="/cb/*" element={<Home />} />
</Route>
<Route element={
<FadeInContentTransition reanimateKey={location.pathname}>
<Outlet />
</FadeInContentTransition>
}>
<Route path="/login" element={<Login />} />
<Route path="/login-state" element={<LoginState />} />
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
</Suspense>
</I18nextProvider>
</>
);
}

Expand Down
31 changes: 31 additions & 0 deletions src/components/Notifications/BackgroundNotificationClickHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useEffect } from 'react';

const BackgroundNotificationClickHandler = () => {
useEffect(() => {
const handleNotificationClickMessage = (event) => {
console.log('Message received from service worker:', event);

if (event.data?.type === 'navigate') {
const targetUrl = event.data.url || '/';
console.log(`Redirecting to: ${targetUrl}`);
window.location.href = targetUrl; // Navigate to the target URL
}
};

// Add the service worker message listener
if (navigator?.serviceWorker) {
navigator.serviceWorker.addEventListener('message', handleNotificationClickMessage);
}

// Cleanup the listener on unmount
return () => {
if (navigator?.serviceWorker) {
navigator.serviceWorker.removeEventListener('message', handleNotificationClickMessage);
}
};
}, []);

return null; // This component does not render any UI
};

export default BackgroundNotificationClickHandler;
73 changes: 0 additions & 73 deletions src/components/Notifications/HandlerNotification.js

This file was deleted.

50 changes: 50 additions & 0 deletions src/components/Notifications/NewCredentialNotification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React, { useEffect, useCallback } from 'react';
import toast, { Toaster } from 'react-hot-toast';
import { AiOutlineClose } from 'react-icons/ai';
import Logo from '../Logo/Logo';
import { useLocation } from "react-router-dom";

const ToastDisplay = ({ id, notification }) => {
return (
<div
className="flex justify-between items-center p-3 bg-white rounded-lg text-gray-600 max-w-3xl mx-auto cursor-pointer"
onClick={() => window.location.href = '/'}
>
<div className="w-1/3 flex items-center justify-start mr-6">
<Logo />
</div>
<div className="flex-grow text-center">
<p className="font-bold text-lg">{notification?.title}</p>
<p>{notification?.body}</p>
</div>
<button onClick={(e) => {
toast.dismiss(id);
e.stopPropagation();
}}
className="focus:outline-none ml-6"
>
<AiOutlineClose size={24} />
</button>
</div>
);
};

const NewCredentialNotification = ({ notification, clearNotification }) => {
const location = useLocation();
const showToast = useCallback(() => {
toast((t) => <ToastDisplay id={t.id} notification={notification} />);
clearNotification();
}, [notification, clearNotification]);

useEffect(() => {
if (notification && location.pathname === '/') {
showToast();
}
}, [notification, location, showToast]);

return (
<Toaster />
);
};

export default NewCredentialNotification;
23 changes: 12 additions & 11 deletions src/firebase.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ export async function isEnabledAndIsSupported() {
export async function register() {
if (await isEnabledAndIsSupported() && 'serviceWorker' in navigator) {
try {
const existingRegistration = await navigator.serviceWorker.getRegistration('/notifications/');
const existingRegistration = await navigator.serviceWorker.getRegistration('/firebase-cloud-messaging-push-scope');
if (existingRegistration) {
console.log('Service Worker is already registered. Scope:', existingRegistration.scope);
} else {
const registration = await navigator.serviceWorker.register('/firebase-messaging-sw.js', { scope: '/notifications/' });
const registration = await navigator.serviceWorker.register('/firebase-messaging-sw.js', { scope: '/firebase-cloud-messaging-push-scope' });
console.log('App: Firebase Messaging Service Worker registered! Scope is:', registration.scope);
}
} catch (err) {
Expand Down Expand Up @@ -70,7 +70,7 @@ const reRegisterServiceWorkerAndGetToken = async () => {
if ('serviceWorker' in navigator) {
try {
// Re-register the service worker
const registration = await navigator.serviceWorker.register('/firebase-messaging-sw.js', { scope: '/notifications/' });
const registration = await navigator.serviceWorker.register('/firebase-messaging-sw.js', { scope: '/firebase-cloud-messaging-push-scope' });
if (registration) {
console.log('Service Worker re-registered', registration);
const token = await requestForToken();
Expand Down Expand Up @@ -112,14 +112,15 @@ export const fetchToken = async () => {
return null; // Return null in case of failure
};

export const onMessageListener = () =>
new Promise(async (resolve) => {
if (await isEnabledAndIsSupported()) {
onMessage(messaging, (payload) => {
resolve(payload);
});
}
});
export const onMessageListener = async (callback) => {
if (await isEnabledAndIsSupported()) {
onMessage(messaging, (payload) => {
callback(payload);
});
} else {
console.error('Messaging is not supported or enabled');
}
};

const initializeFirebaseAndMessaging = async () => {
if (notificationApiIsSupported) {
Expand Down
Loading
Loading