Notifications: Request permission after user gesture (#3132)

This commit is contained in:
Alexander Zinchuk 2023-05-02 15:23:43 +04:00
parent 894eb66e7e
commit 43eb7e7020
3 changed files with 41 additions and 12 deletions

View File

@ -6,7 +6,7 @@ import { getActions, withGlobal } from '../../../global';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import { playNotifySound } from '../../../util/notifications'; import { playNotifySound, checkIfNotificationsSupported } from '../../../util/notifications';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import RangeSlider from '../../ui/RangeSlider'; import RangeSlider from '../../ui/RangeSlider';
@ -56,6 +56,8 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
const runDebounced = useRunDebounced(500, true); const runDebounced = useRunDebounced(500, true);
const areNotificationsSupported = checkIfNotificationsSupported();
const handleSettingsChange = useCallback(( const handleSettingsChange = useCallback((
e: ChangeEvent<HTMLInputElement>, e: ChangeEvent<HTMLInputElement>,
peerType: 'contact' | 'group' | 'broadcast', peerType: 'contact' | 'group' | 'broadcast',
@ -147,11 +149,12 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
subLabel={lang(hasWebNotifications ? 'UserInfo.NotificationsEnabled' : 'UserInfo.NotificationsDisabled')} subLabel={lang(hasWebNotifications ? 'UserInfo.NotificationsEnabled' : 'UserInfo.NotificationsDisabled')}
checked={hasWebNotifications} checked={hasWebNotifications}
disabled={!areNotificationsSupported}
onChange={handleWebNotificationsChange} onChange={handleWebNotificationsChange}
/> />
<Checkbox <Checkbox
label="Offline notifications" label="Offline notifications"
disabled={!hasWebNotifications} disabled={!hasWebNotifications || !areNotificationsSupported}
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
subLabel={lang(hasPushNotifications ? 'UserInfo.NotificationsEnabled' : 'UserInfo.NotificationsDisabled')} subLabel={lang(hasPushNotifications ? 'UserInfo.NotificationsEnabled' : 'UserInfo.NotificationsDisabled')}
checked={hasPushNotifications} checked={hasPushNotifications}
@ -162,6 +165,7 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
label="Sound" label="Sound"
min={0} min={0}
max={10} max={10}
disabled={!areNotificationsSupported}
value={notificationSoundVolume} value={notificationSoundVolume}
onChange={handleVolumeChange} onChange={handleVolumeChange}
/> />

View File

@ -86,7 +86,17 @@ addActionHandler('initShared', (): ActionReturnType => {
addActionHandler('initMain', (global): ActionReturnType => { addActionHandler('initMain', (global): ActionReturnType => {
const { hasWebNotifications, hasPushNotifications } = selectNotifySettings(global); const { hasWebNotifications, hasPushNotifications } = selectNotifySettings(global);
if (hasWebNotifications && hasPushNotifications) { if (hasWebNotifications && hasPushNotifications) {
void subscribe(); // Most of the browsers only show the notifications permission prompt after the first user gesture
const events = ['click', 'ontouchstart', 'keypress'];
const subscribeAfterUserGesture = () => {
void subscribe();
events.forEach((event) => {
document.removeEventListener(event, subscribeAfterUserGesture);
});
};
events.forEach((event) => {
document.addEventListener(event, subscribeAfterUserGesture, { once: true });
});
} }
}); });

View File

@ -73,7 +73,7 @@ function checkIfPushSupported() {
return true; return true;
} }
function checkIfNotificationsSupported() { export function checkIfNotificationsSupported() {
// Let's check if the browser supports notifications // Let's check if the browser supports notifications
if (!('Notification' in window)) { if (!('Notification' in window)) {
if (DEBUG) { if (DEBUG) {
@ -137,20 +137,27 @@ function checkIfShouldResubscribe(subscription: PushSubscription | null) {
async function requestPermission() { async function requestPermission() {
if (!('Notification' in window)) return; if (!('Notification' in window)) return;
if (!['granted', 'denied'].includes(Notification.permission)) { let permission = Notification.permission;
await Notification.requestPermission(); if (!['granted', 'denied'].includes(permission)) {
permission = await Notification.requestPermission();
} }
const isGranted = permission === 'granted';
const { updateWebNotificationSettings } = getActions();
updateWebNotificationSettings({
hasWebNotifications: isGranted,
hasPushNotifications: isGranted,
});
} }
async function unsubscribeFromPush(subscription: PushSubscription | null) { async function unsubscribeFromPush(subscription: PushSubscription | null) {
const global = getGlobal(); const global = getGlobal();
const dispatch = getActions(); const { deleteDeviceToken } = getActions();
if (subscription) { if (subscription) {
try { try {
const deviceToken = getDeviceToken(subscription); const deviceToken = getDeviceToken(subscription);
await callApi('unregisterDevice', deviceToken); await callApi('unregisterDevice', deviceToken);
await subscription.unsubscribe(); await subscription.unsubscribe();
dispatch.deleteDeviceToken(); deleteDeviceToken();
return; return;
} catch (error) { } catch (error) {
if (DEBUG) { if (DEBUG) {
@ -161,7 +168,7 @@ async function unsubscribeFromPush(subscription: PushSubscription | null) {
} }
if (global.push) { if (global.push) {
await callApi('unregisterDevice', global.push.deviceToken); await callApi('unregisterDevice', global.push.deviceToken);
dispatch.deleteDeviceToken(); deleteDeviceToken();
} }
} }
@ -226,6 +233,7 @@ export async function subscribe() {
let subscription = await serviceWorkerRegistration.pushManager.getSubscription(); let subscription = await serviceWorkerRegistration.pushManager.getSubscription();
if (!checkIfShouldResubscribe(subscription)) return; if (!checkIfShouldResubscribe(subscription)) return;
await unsubscribeFromPush(subscription); await unsubscribeFromPush(subscription);
const { setDeviceToken, updateWebNotificationSettings } = getActions();
try { try {
subscription = await serviceWorkerRegistration.pushManager.subscribe({ subscription = await serviceWorkerRegistration.pushManager.subscribe({
userVisibleOnly: true, userVisibleOnly: true,
@ -236,10 +244,13 @@ export async function subscribe() {
console.log('[PUSH] Received push subscription: ', deviceToken); console.log('[PUSH] Received push subscription: ', deviceToken);
} }
await callApi('registerDevice', deviceToken); await callApi('registerDevice', deviceToken);
getActions() setDeviceToken(deviceToken);
.setDeviceToken(deviceToken); updateWebNotificationSettings({
hasWebNotifications: true,
hasPushNotifications: true,
});
} catch (error: any) { } catch (error: any) {
if (Notification.permission === 'denied' as NotificationPermission) { if (Notification.permission === 'denied') {
// The user denied the notification permission which // The user denied the notification permission which
// means we failed to subscribe and the user will need // means we failed to subscribe and the user will need
// to manually change the notification permission to // to manually change the notification permission to
@ -261,6 +272,10 @@ export async function subscribe() {
await requestPermission(); await requestPermission();
} }
} }
updateWebNotificationSettings({
hasWebNotifications: false,
hasPushNotifications: false,
});
} }
} }