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:^"