diff --git a/.changeset/tasty-apes-learn.md b/.changeset/tasty-apes-learn.md
new file mode 100644
index 0000000000..7a4545cf10
--- /dev/null
+++ b/.changeset/tasty-apes-learn.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-notifications': patch
+---
+
+Fix to show web notifications even when browser is on foreground. Fix duplicate notifications with multiple tabs.
diff --git a/plugins/notifications/api-report.md b/plugins/notifications/api-report.md
index f1369a882d..e998d76ea4 100644
--- a/plugins/notifications/api-report.md
+++ b/plugins/notifications/api-report.md
@@ -175,8 +175,9 @@ export function useTitleCounter(): {
};
// @public (undocumented)
-export function useWebNotifications(): {
+export function useWebNotifications(enabled: boolean): {
sendWebNotification: (options: {
+ id: string;
title: string;
description: string;
link?: string;
diff --git a/plugins/notifications/dev/index.tsx b/plugins/notifications/dev/index.tsx
index b601c4b9a4..a21170aec0 100644
--- a/plugins/notifications/dev/index.tsx
+++ b/plugins/notifications/dev/index.tsx
@@ -20,9 +20,11 @@ import {
notificationsPlugin,
NotificationsSidebarItem,
} from '../src';
+import { signalsPlugin } from '@backstage/plugin-signals';
createDevApp()
.registerPlugin(notificationsPlugin)
+ .registerPlugin(signalsPlugin)
.addPage({
element: (
)
+ .addSidebarItem()
.render();
diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json
index ee2bdc885a..8c3a90c092 100644
--- a/plugins/notifications/package.json
+++ b/plugins/notifications/package.json
@@ -50,6 +50,7 @@
"@backstage/cli": "workspace:^",
"@backstage/core-app-api": "workspace:^",
"@backstage/dev-utils": "workspace:^",
+ "@backstage/plugin-signals": "workspace:^",
"@backstage/test-utils": "workspace:^",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^15.0.0",
diff --git a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx
index d143344a9a..b15e5a34c6 100644
--- a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx
+++ b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx
@@ -51,7 +51,7 @@ export const NotificationsSidebarItem = (props?: {
const notificationsRoute = useRouteRef(rootRouteRef);
// TODO: Do we want to add long polling in case signals are not available
const { lastSignal } = useSignal('notifications');
- const { sendWebNotification } = useWebNotifications();
+ const { sendWebNotification } = useWebNotifications(webNotificationsEnabled);
const [refresh, setRefresh] = React.useState(false);
const { setNotificationCount } = useTitleCounter();
@@ -75,6 +75,7 @@ export const NotificationsSidebarItem = (props?: {
return;
}
sendWebNotification({
+ id: notification.id,
title: notification.payload.title,
description: notification.payload.description ?? '',
link: notification.payload.link,
diff --git a/plugins/notifications/src/hooks/useWebNotifications.ts b/plugins/notifications/src/hooks/useWebNotifications.ts
index bf3b5269f5..728d992130 100644
--- a/plugins/notifications/src/hooks/useWebNotifications.ts
+++ b/plugins/notifications/src/hooks/useWebNotifications.ts
@@ -14,49 +14,58 @@
* limitations under the License.
*/
import { useCallback, useEffect, useState } from 'react';
+import { rootRouteRef } from '../routes';
+import { useRouteRef } from '@backstage/core-plugin-api';
+import { useNavigate } from 'react-router-dom';
/** @public */
-export function useWebNotifications() {
+export function useWebNotifications(enabled: boolean) {
const [webNotificationPermission, setWebNotificationPermission] =
useState('default');
- const [webNotifications, setWebNotifications] = useState([]);
+ const notificationsRoute = useRouteRef(rootRouteRef);
+ const navigate = useNavigate();
useEffect(() => {
- if ('Notification' in window && webNotificationPermission === 'default') {
+ if (
+ enabled &&
+ 'Notification' in window &&
+ webNotificationPermission === 'default'
+ ) {
window.Notification.requestPermission().then(permission => {
setWebNotificationPermission(permission);
});
}
- }, [webNotificationPermission]);
-
- document.addEventListener('visibilitychange', () => {
- if (document.visibilityState === 'visible') {
- webNotifications.forEach(n => n.close());
- setWebNotifications([]);
- }
- });
+ }, [enabled, webNotificationPermission]);
const sendWebNotification = useCallback(
- (options: { title: string; description: string; link?: string }) => {
+ (options: {
+ id: string;
+ title: string;
+ description: string;
+ link?: string;
+ }) => {
if (webNotificationPermission !== 'granted') {
return null;
}
const notification = new Notification(options.title, {
body: options.description,
+ tag: options.id, // Prevent duplicates from multiple tabs
});
notification.onclick = event => {
event.preventDefault();
- notification.close();
if (options.link) {
window.open(options.link, '_blank');
+ } else {
+ navigate(notificationsRoute());
}
+ notification.close();
};
return notification;
},
- [webNotificationPermission],
+ [webNotificationPermission, navigate, notificationsRoute],
);
return { sendWebNotification };
diff --git a/yarn.lock b/yarn.lock
index af96b924bd..a07e9249f7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6057,6 +6057,7 @@ __metadata:
"@backstage/dev-utils": "workspace:^"
"@backstage/errors": "workspace:^"
"@backstage/plugin-notifications-common": "workspace:^"
+ "@backstage/plugin-signals": "workspace:^"
"@backstage/plugin-signals-react": "workspace:^"
"@backstage/test-utils": "workspace:^"
"@backstage/theme": "workspace:^"