diff --git a/web/src/components/App.jsx b/web/src/components/App.jsx index 6c4761f1..e174ccc4 100644 --- a/web/src/components/App.jsx +++ b/web/src/components/App.jsx @@ -8,7 +8,6 @@ import { AllSubscriptions, SingleSubscription } from "./Notifications"; import themeOptions, { darkPalette, lightPalette } from "./theme"; import Navigation from "./Navigation"; import ActionBar from "./ActionBar"; -import notifier from "../app/Notifier"; import Preferences from "./Preferences"; import subscriptionManager from "../app/SubscriptionManager"; import userManager from "../app/UserManager"; @@ -91,7 +90,6 @@ const Layout = () => { const params = useParams(); const { account, setAccount } = useContext(AccountContext); const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false); - const [notificationsGranted, setNotificationsGranted] = useState(notifier.granted()); const [sendDialogOpenMode, setSendDialogOpenMode] = useState(""); const users = useLiveQuery(() => userManager.all()); const subscriptions = useLiveQuery(() => subscriptionManager.all()); @@ -115,10 +113,8 @@ const Layout = () => { setMobileDrawerOpen(!mobileDrawerOpen)} - onNotificationGranted={setNotificationsGranted} onPublishMessageClick={() => setSendDialogOpenMode(PublishDialog.OPEN_MODE_DEFAULT)} />
diff --git a/web/src/components/Navigation.jsx b/web/src/components/Navigation.jsx index fe1cf8be..ad671d99 100644 --- a/web/src/components/Navigation.jsx +++ b/web/src/components/Navigation.jsx @@ -43,6 +43,7 @@ import UpgradeDialog from "./UpgradeDialog"; import { AccountContext } from "./App"; import { PermissionDenyAll, PermissionRead, PermissionReadWrite, PermissionWrite } from "./ReserveIcons"; import { SubscriptionPopup } from "./SubscriptionPopup"; +import { useNotificationPermissionListener } from "./hooks"; const navWidth = 280; @@ -109,17 +110,12 @@ const NavList = (props) => { const isPaid = account?.billing?.subscription; const showUpgradeBanner = config.enable_payments && !isAdmin && !isPaid; const showSubscriptionsList = props.subscriptions?.length > 0; - const [showNotificationPermissionRequired, setShowNotificationPermissionRequired] = useState(notifier.notRequested()); - const [showNotificationPermissionDenied, setShowNotificationPermissionDenied] = useState(notifier.denied()); + const showNotificationPermissionRequired = useNotificationPermissionListener(() => notifier.notRequested()); + const showNotificationPermissionDenied = useNotificationPermissionListener(() => notifier.denied()); const showNotificationIOSInstallRequired = notifier.iosSupportedButInstallRequired(); const showNotificationBrowserNotSupportedBox = !showNotificationIOSInstallRequired && !notifier.browserSupported(); const showNotificationContextNotSupportedBox = notifier.browserSupported() && !notifier.contextSupported(); // Only show if notifications are generally supported in the browser - const refreshPermissions = () => { - setShowNotificationPermissionRequired(notifier.notRequested()); - setShowNotificationPermissionDenied(notifier.denied()); - }; - const alertVisible = showNotificationPermissionRequired || showNotificationPermissionDenied || @@ -131,7 +127,7 @@ const NavList = (props) => { <> - {showNotificationPermissionRequired && } + {showNotificationPermissionRequired && } {showNotificationPermissionDenied && } {showNotificationBrowserNotSupportedBox && } {showNotificationContextNotSupportedBox && } @@ -354,11 +350,10 @@ const SubscriptionItem = (props) => { ); }; -const NotificationPermissionRequired = ({ refreshPermissions }) => { +const NotificationPermissionRequired = () => { const { t } = useTranslation(); const requestPermission = async () => { await notifier.maybeRequestPermission(); - refreshPermissions(); }; return ( diff --git a/web/src/components/Preferences.jsx b/web/src/components/Preferences.jsx index add9b8c0..a24ccd96 100644 --- a/web/src/components/Preferences.jsx +++ b/web/src/components/Preferences.jsx @@ -49,7 +49,7 @@ import { ReserveAddDialog, ReserveDeleteDialog, ReserveEditDialog } from "./Rese import { UnauthorizedError } from "../app/errors"; import { subscribeTopic } from "./SubscribeDialog"; import notifier from "../app/Notifier"; -import { useIsLaunchedPWA } from "./hooks"; +import { useIsLaunchedPWA, useNotificationPermissionListener } from "./hooks"; const maybeUpdateAccountSettings = async (payload) => { if (!session.exists()) { @@ -79,6 +79,7 @@ const Preferences = () => ( const Notifications = () => { const { t } = useTranslation(); const isLaunchedPWA = useIsLaunchedPWA(); + const pushPossible = useNotificationPermissionListener(() => notifier.pushPossible()); return ( @@ -89,7 +90,7 @@ const Notifications = () => { - {!isLaunchedPWA && notifier.pushPossible() && } + {!isLaunchedPWA && pushPossible && } ); diff --git a/web/src/components/hooks.js b/web/src/components/hooks.js index 5ff375d2..6d5f3d51 100644 --- a/web/src/components/hooks.js +++ b/web/src/components/hooks.js @@ -136,8 +136,31 @@ export const useAutoSubscribe = (subscriptions, selected) => { }; const webPushBroadcastChannel = new BroadcastChannel("web-push-broadcast"); -const matchMedia = window.matchMedia("(display-mode: standalone)"); -const isIOSStandalone = window.navigator.standalone === true; + +/** + * Hook to return a value that's refreshed when the notification permission changes + */ +export const useNotificationPermissionListener = (query) => { + const [result, setResult] = useState(query()); + + useEffect(() => { + const handler = () => { + setResult(query()); + }; + + if ("permissions" in navigator) { + navigator.permissions.query({ name: "notifications" }).then((permission) => { + permission.addEventListener("change", handler); + + return () => { + permission.removeEventListener("change", handler); + }; + }); + } + }, []); + + return result; +}; /** * Updates the Web Push subscriptions when the list of topics changes, @@ -146,10 +169,11 @@ const isIOSStandalone = window.navigator.standalone === true; */ const useWebPushListener = (topics) => { const [lastTopics, setLastTopics] = useState(); + const pushPossible = useNotificationPermissionListener(() => notifier.pushPossible()); useEffect(() => { const topicsChanged = JSON.stringify(topics) !== JSON.stringify(lastTopics); - if (!notifier.pushPossible() || !topicsChanged) { + if (!pushPossible || !topicsChanged) { return; } @@ -183,25 +207,7 @@ const useWebPushListener = (topics) => { * automatically. */ export const useWebPushTopics = () => { - const [pushPossible, setPushPossible] = useState(notifier.pushPossible()); - - useEffect(() => { - const handler = () => { - const newPushPossible = notifier.pushPossible(); - console.log(`[useWebPushTopics] Notification Permission changed`, { pushPossible: newPushPossible }); - setPushPossible(newPushPossible); - }; - - if ("permissions" in navigator) { - navigator.permissions.query({ name: "notifications" }).then((permission) => { - permission.addEventListener("change", handler); - - return () => { - permission.removeEventListener("change", handler); - }; - }); - } - }); + const pushPossible = useNotificationPermissionListener(() => notifier.pushPossible()); const topics = useLiveQuery( async () => subscriptionManager.webPushTopics(pushPossible), @@ -214,6 +220,9 @@ export const useWebPushTopics = () => { return topics; }; +const matchMedia = window.matchMedia("(display-mode: standalone)"); +const isIOSStandalone = window.navigator.standalone === true; + /* * Watches the "display-mode" to detect if the app is running as a standalone app (PWA). */