Merge branch 'gek/ios-browser-notifications' into develop

This commit is contained in:
Gabe Kangas 2023-06-05 21:35:20 -07:00
commit d7ed23e153
No known key found for this signature in database
GPG key ID: 4345B2060657F330
4 changed files with 88 additions and 9 deletions

View file

@ -1,5 +1,7 @@
import { Row, Spin, Typography, Button } from 'antd'; import { Row, Spin, Typography, Button } from 'antd';
import React, { FC, useState } from 'react'; import React, { FC, useState } from 'react';
import UploadOutlined from '@ant-design/icons/lib/icons/UploadOutlined';
import PlusSquareOutlined from '@ant-design/icons/lib/icons/PlusSquareOutlined';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { ErrorBoundary } from 'react-error-boundary'; import { ErrorBoundary } from 'react-error-boundary';
import { accessTokenAtom, clientConfigStateAtom } from '../../stores/ClientConfigStore'; import { accessTokenAtom, clientConfigStateAtom } from '../../stores/ClientConfigStore';
@ -8,9 +10,11 @@ import {
saveNotificationRegistration, saveNotificationRegistration,
} from '../../../services/notifications-service'; } from '../../../services/notifications-service';
import styles from './BrowserNotifyModal.module.scss'; import styles from './BrowserNotifyModal.module.scss';
import isPushNotificationSupported from '../../../utils/browserPushNotifications';
import { ComponentError } from '../../ui/ComponentError/ComponentError'; import { ComponentError } from '../../ui/ComponentError/ComponentError';
import { isMobileSafariHomeScreenApp, isMobileSafariIos } from '../../../utils/helpers';
import { arePushNotificationSupported } from '../../../utils/browserPushNotifications';
const { Title } = Typography; const { Title } = Typography;
const NotificationsNotSupported = () => ( const NotificationsNotSupported = () => (
@ -21,6 +25,31 @@ const NotificationsNotSupportedLocal = () => (
<div>Browser notifications are not supported for local servers.</div> <div>Browser notifications are not supported for local servers.</div>
); );
const MobileSafariInstructions = () => (
<div>
<Title level={3}>Get notified on iOS</Title>
It takes a couple extra steps to make sure you get notified when your favorite streams go live.
<ol>
<li>
Tap the <strong>share</strong> button <UploadOutlined /> in Safari.
</li>
<li>
Scroll down and tap <strong>&ldquo;Add to Home Screen&rdquo;</strong> <PlusSquareOutlined />
.
</li>
<li>
Tap <strong>&ldquo;Add&rdquo;</strong>.
</li>
<li>Give this link a name and tap the new icon on your home screen</li>
<li>Come back to this screen and enable notifications.</li>
<li>
Tap <strong>&ldquo;Allow&rdquo;</strong> when prompted.
</li>
</ol>
</div>
);
export type PermissionPopupPreviewProps = { export type PermissionPopupPreviewProps = {
start: () => void; start: () => void;
}; };
@ -78,22 +107,27 @@ export const BrowserNotifyModal = () => {
const [browserPushPermissionsPending, setBrowserPushPermissionsPending] = const [browserPushPermissionsPending, setBrowserPushPermissionsPending] =
useState<boolean>(false); useState<boolean>(false);
const notificationsPermitted = const notificationsPermitted =
isPushNotificationSupported() && Notification.permission !== 'default'; arePushNotificationSupported() && Notification.permission !== 'default';
const { notifications } = config; const { notifications } = config;
const { browser } = notifications; const { browser } = notifications;
const { publicKey } = browser; const { publicKey } = browser;
const browserPushSupported = browser.enabled && isPushNotificationSupported(); const browserPushSupported =
browser.enabled && (arePushNotificationSupported() || isMobileSafariHomeScreenApp());
// If notification permissions are granted, show user info how to disable them // If notification permissions are granted, show user info how to disable them
if (notificationsPermitted) { if (notificationsPermitted) {
return <NotificationsEnabled />; return <NotificationsEnabled />;
} }
if (isMobileSafariIos() && !isMobileSafariHomeScreenApp()) {
return <MobileSafariInstructions />;
}
const startBrowserPushRegistration = async () => { const startBrowserPushRegistration = async () => {
// If notification permissions are already denied or granted, don't do anything. // If notification permissions are already denied or granted, don't do anything.
if (isPushNotificationSupported() && Notification.permission !== 'default') { if (arePushNotificationSupported() && Notification.permission !== 'default') {
return; return;
} }

View file

@ -6,7 +6,7 @@ import dynamic from 'next/dynamic';
import classnames from 'classnames'; import classnames from 'classnames';
import ActionButtons from './ActionButtons'; import ActionButtons from './ActionButtons';
import { LOCAL_STORAGE_KEYS, getLocalStorage, setLocalStorage } from '../../../utils/localStorage'; import { LOCAL_STORAGE_KEYS, getLocalStorage, setLocalStorage } from '../../../utils/localStorage';
import isPushNotificationSupported from '../../../utils/browserPushNotifications'; import { canPushNotificationsBeSupported } from '../../../utils/browserPushNotifications';
import { import {
clientConfigStateAtom, clientConfigStateAtom,
@ -179,7 +179,9 @@ export const Content: FC = () => {
useEffect(() => { useEffect(() => {
// isPushNotificationSupported relies on `navigator` so that needs to be // isPushNotificationSupported relies on `navigator` so that needs to be
// fired from this useEffect. // fired from this useEffect.
setSupportsBrowserNotifications(isPushNotificationSupported() && browserNotificationsEnabled); setSupportsBrowserNotifications(
canPushNotificationsBeSupported() && browserNotificationsEnabled,
);
}, [browserNotificationsEnabled]); }, [browserNotificationsEnabled]);
const showChat = isChatAvailable && !chatDisabled && isChatVisible; const showChat = isChatAvailable && !chatDisabled && isChatVisible;
@ -238,7 +240,7 @@ export const Content: FC = () => {
<Col span={24} style={{ paddingRight: dynamicPadding }}> <Col span={24} style={{ paddingRight: dynamicPadding }}>
<ActionButtons <ActionButtons
supportFediverseFeatures={supportFediverseFeatures} supportFediverseFeatures={supportFediverseFeatures}
supportsBrowserNotifications={supportsBrowserNotifications} supportsBrowserNotifications
showNotifyReminder={showNotifyReminder} showNotifyReminder={showNotifyReminder}
setShowNotifyModal={setShowNotifyModal} setShowNotifyModal={setShowNotifyModal}
disableNotifyReminderPopup={disableNotifyReminderPopup} disableNotifyReminderPopup={disableNotifyReminderPopup}

View file

@ -1,3 +1,15 @@
export default function isPushNotificationSupported() { import { isMobileSafariIos } from './helpers';
return 'serviceWorker' in navigator && 'PushManager' in window;
export function arePushNotificationSupported() {
return 'Notification' in window && 'serviceWorker' in navigator && 'PushManager' in window;
}
export function canPushNotificationsBeSupported() {
// Mobile safari will return false for supporting push notifications, but
// it does support them. So we need to check for mobile safari and return
// true if it is.
if (isMobileSafariIos()) {
return true;
}
return 'Notification' in window && 'serviceWorker' in navigator && 'PushManager' in window;
} }

View file

@ -1,3 +1,5 @@
import UAParser from 'ua-parser-js';
export function pluralize(string, count) { export function pluralize(string, count) {
if (count === 1) { if (count === 1) {
return string; return string;
@ -20,3 +22,32 @@ export function mergeMeta(meta) {
return acc; return acc;
}, {}); }, {});
} }
export const isMobileSafariIos = () => {
try {
const ua = navigator.userAgent;
const uaParser = new UAParser(ua);
const browser = uaParser.getBrowser();
const device = uaParser.getDevice();
if (device.vendor !== 'Apple') {
return false;
}
if (device.type !== 'mobile' && device.type !== 'tablet') {
return false;
}
return browser.name === 'Mobile Safari' || browser.name === 'Safari';
} catch (e) {
return false;
}
};
export const isMobileSafariHomeScreenApp = () => {
if (!isMobileSafariIos()) {
return false;
}
return 'standalone' in window.navigator && window.navigator.standalone;
};