diff --git a/web/push-notif/push-notifs-handler.js b/web/push-notif/push-notifs-handler.js index f8e39c28e..dd83fc514 100644 --- a/web/push-notif/push-notifs-handler.js +++ b/web/push-notif/push-notifs-handler.js @@ -1,93 +1,124 @@ // @flow import * as React from 'react'; +import { useDispatch } from 'react-redux'; import { setDeviceToken, setDeviceTokenActionTypes, } from 'lib/actions/device-actions.js'; import { useModalContext } from 'lib/components/modal-provider.react.js'; import { isLoggedIn } from 'lib/selectors/user-selectors.js'; import { useDispatchActionPromise, useServerCall, } from 'lib/utils/action-utils.js'; import electron from '../electron.js'; import PushNotifModal from '../modals/push-notif-modal.react.js'; +import { updateNavInfoActionType } from '../redux/action-types.js'; import { useSelector } from '../redux/redux-utils.js'; function useCreatePushSubscription(): () => Promise { const publicKey = useSelector(state => state.pushApiPublicKey); const dispatchActionPromise = useDispatchActionPromise(); const callSetDeviceToken = useServerCall(setDeviceToken); return React.useCallback(async () => { if (!publicKey) { return; } const workerRegistration = await navigator.serviceWorker?.ready; if (!workerRegistration) { return; } const subscription = await workerRegistration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: publicKey, }); dispatchActionPromise( setDeviceTokenActionTypes, callSetDeviceToken(JSON.stringify(subscription)), ); }, [callSetDeviceToken, dispatchActionPromise, publicKey]); } function PushNotificationsHandler(): React.Node { const createPushSubscription = useCreatePushSubscription(); const modalContext = useModalContext(); const loggedIn = useSelector(isLoggedIn); + const dispatch = useDispatch(); + React.useEffect(() => { (async () => { if (!navigator.serviceWorker || electron) { return; } await navigator.serviceWorker.register('/worker/notif', { scope: '/' }); if (Notification.permission === 'granted') { // Make sure the subscription is current if we have the permissions await createPushSubscription(); } else if (Notification.permission === 'default' && loggedIn) { // Ask existing users that are already logged in for permission modalContext.pushModal(); } })(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Ask for permission on login const prevLoggedIn = React.useRef(loggedIn); React.useEffect(() => { if (!navigator.serviceWorker || electron) { return; } if (!prevLoggedIn.current && loggedIn) { if (Notification.permission === 'granted') { createPushSubscription(); } else if (Notification.permission === 'default') { modalContext.pushModal(); } } prevLoggedIn.current = loggedIn; }, [createPushSubscription, loggedIn, modalContext, prevLoggedIn]); + // Redirect to thread on notification click + React.useEffect(() => { + if (!navigator.serviceWorker || electron) { + return; + } + + const callback = (event: MessageEvent) => { + if (typeof event.data !== 'object' || !event.data) { + return; + } + + if (event.data.targetThreadID) { + const payload = { + chatMode: 'view', + activeChatThreadID: event.data.targetThreadID, + tab: 'chat', + }; + + dispatch({ type: updateNavInfoActionType, payload }); + } + }; + + navigator.serviceWorker.addEventListener('message', callback); + return () => + navigator.serviceWorker?.removeEventListener('message', callback); + }, [dispatch]); + return null; } export { PushNotificationsHandler, useCreatePushSubscription }; diff --git a/web/push-notif/service-worker.js b/web/push-notif/service-worker.js index e7167c056..786a5f81b 100644 --- a/web/push-notif/service-worker.js +++ b/web/push-notif/service-worker.js @@ -1,48 +1,74 @@ // @flow import type { WebNotification } from 'lib/types/notif-types.js'; declare class PushMessageData { json(): Object; } declare class PushEvent extends ExtendableEvent { +data: PushMessageData; } declare var clients: Clients; declare function skipWaiting(): Promise; self.addEventListener('install', () => { skipWaiting(); }); self.addEventListener('activate', (event: ExtendableEvent) => { event.waitUntil(clients.claim()); }); self.addEventListener('push', (event: PushEvent) => { const data: WebNotification = event.data.json(); event.waitUntil( (async () => { let body = data.body; if (data.prefix) { body = `${data.prefix} ${body}`; } await self.registration.showNotification(data.title, { body, badge: 'https://web.comm.app/favicon.ico', icon: 'https://web.comm.app/favicon.ico', tag: data.id, data: { unreadCount: data.unreadCount, threadID: data.threadID, }, }); })(), ); }); self.addEventListener('notificationclick', (event: NotificationEvent) => { event.notification.close(); + event.waitUntil( + (async () => { + const clientList: Array = (await clients.matchAll({ + type: 'window', + }): any); + + const selectedClient = + clientList.find(client => client.focused) ?? clientList[0]; + + if (selectedClient) { + if (!selectedClient.focused) { + await selectedClient.focus(); + } + selectedClient.postMessage({ + targetThreadID: event.notification.data.threadID, + }); + } else { + const url = + (process.env.NODE_ENV === 'production' + ? 'https://web.comm.app' + : 'http://localhost:3000/comm') + + `/chat/thread/${event.notification.data.threadID}/`; + clients.openWindow(url); + } + })(), + ); });