Add hints if an error occurs subscribing to notifications (#143)
* Add hints if an error occurs subscribing to notifications * Update import (type/linting issue) * disable optimization for debugging * Revert "disable optimization for debugging" This reverts commit 5b837f94e97b7488a7dc565e7f74d399e19c286f. * improve detection of notification support + improve ux / ui surrounding that * update translations
This commit is contained in:
parent
fa029e0c09
commit
704452732a
14 changed files with 249 additions and 117 deletions
|
@ -2200,6 +2200,10 @@
|
||||||
"Dismiss": "Dismiss",
|
"Dismiss": "Dismiss",
|
||||||
"Heads up: browser notifications are currently blocked in this browser.": "Heads up: browser notifications are currently blocked in this browser.",
|
"Heads up: browser notifications are currently blocked in this browser.": "Heads up: browser notifications are currently blocked in this browser.",
|
||||||
"To enable push notifications please configure your browser to allow notifications on odysee.com.": "To enable push notifications please configure your browser to allow notifications on odysee.com.",
|
"To enable push notifications please configure your browser to allow notifications on odysee.com.": "To enable push notifications please configure your browser to allow notifications on odysee.com.",
|
||||||
"There was an error enabling browser notifications. Please make sure your browser settings allow you to subscribe to notifications.": "There was an error enabling browser notifications. Please make sure your browser settings allow you to subscribe to notifications.",
|
"Browser notifications aren't supported. Here's a few tips:": "Browser notifications aren't supported. Here's a few tips:",
|
||||||
|
"Notifications aren't available when in incognito or private mode.": "Notifications aren't available when in incognito or private mode.",
|
||||||
|
"On Firefox, notifications won't function if cookies are set to clear on browser close. Please disable or add an exception for Odysee, then refresh.": "On Firefox, notifications won't function if cookies are set to clear on browser close. Please disable or add an exception for Odysee, then refresh.",
|
||||||
|
"For Brave, enable google push notifications in settings.": "For Brave, enable google push notifications in settings.",
|
||||||
|
"Check browser settings to see if notifications are disabled or otherwise restricted.": "Check browser settings to see if notifications are disabled or otherwise restricted.",
|
||||||
"--end--": "--end--"
|
"--end--": "--end--"
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,16 +7,17 @@ type Props = {
|
||||||
subtitle?: string,
|
subtitle?: string,
|
||||||
multirow?: boolean, // Displays the Value widget(s) below the Label instead of on the right.
|
multirow?: boolean, // Displays the Value widget(s) below the Label instead of on the right.
|
||||||
useVerticalSeparator?: boolean, // Show a separator line between Label and Value. Useful when there are multiple Values.
|
useVerticalSeparator?: boolean, // Show a separator line between Label and Value. Useful when there are multiple Values.
|
||||||
|
disabled?: boolean,
|
||||||
children?: React$Node,
|
children?: React$Node,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SettingsRow(props: Props) {
|
export default function SettingsRow(props: Props) {
|
||||||
const { title, subtitle, multirow, useVerticalSeparator, children } = props;
|
const { title, subtitle, multirow, useVerticalSeparator, disabled, children } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classnames('card__main-actions settings__row', {
|
className={classnames('card__main-actions settings__row', {
|
||||||
'section__actions--between': !multirow,
|
'section__actions--between': !multirow,
|
||||||
|
'opacity-40': disabled,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className="settings__row--title">
|
<div className="settings__row--title">
|
||||||
|
|
|
@ -59,7 +59,7 @@ export default function SubscribeButton(props: Props) {
|
||||||
}
|
}
|
||||||
const claimName = channelName && '@' + channelName;
|
const claimName = channelName && '@' + channelName;
|
||||||
|
|
||||||
const { pushSupported, pushEnabled, pushRequest } = useBrowserNotifications();
|
const { pushSupported, pushEnabled, pushRequest, pushErrorModal } = useBrowserNotifications();
|
||||||
|
|
||||||
const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe;
|
const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe;
|
||||||
|
|
||||||
|
@ -125,6 +125,7 @@ export default function SubscribeButton(props: Props) {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{isSubscribed && uiNotificationsEnabled && (
|
{isSubscribed && uiNotificationsEnabled && (
|
||||||
|
<>
|
||||||
<Button
|
<Button
|
||||||
button="alt"
|
button="alt"
|
||||||
icon={notificationsDisabled ? ICONS.BELL : ICONS.BELL_ON}
|
icon={notificationsDisabled ? ICONS.BELL : ICONS.BELL_ON}
|
||||||
|
@ -155,6 +156,8 @@ export default function SubscribeButton(props: Props) {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{pushErrorModal()}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { doClearPublish } from 'redux/actions/publish';
|
||||||
import { Lbryio } from 'lbryinc';
|
import { Lbryio } from 'lbryinc';
|
||||||
import { selectFollowedTagsList } from 'redux/selectors/tags';
|
import { selectFollowedTagsList } from 'redux/selectors/tags';
|
||||||
import { doToast, doError, doNotificationList } from 'redux/actions/notifications';
|
import { doToast, doError, doNotificationList } from 'redux/actions/notifications';
|
||||||
import { pushReconnect, pushDisconnect, pushValidate } from '$web/src/push-notifications';
|
import pushNotifications from '$web/src/push-notifications';
|
||||||
|
|
||||||
import Native from 'native';
|
import Native from 'native';
|
||||||
import {
|
import {
|
||||||
|
@ -537,8 +537,10 @@ export function doSignIn() {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const user = selectUser(state);
|
const user = selectUser(state);
|
||||||
|
|
||||||
pushReconnect(user.id);
|
if (pushNotifications.supported) {
|
||||||
pushValidate(user.id);
|
pushNotifications.reconnect(user.id);
|
||||||
|
pushNotifications.validate(user.id);
|
||||||
|
}
|
||||||
|
|
||||||
const notificationsEnabled = SIMPLE_SITE || user.experimental_ui;
|
const notificationsEnabled = SIMPLE_SITE || user.experimental_ui;
|
||||||
|
|
||||||
|
@ -562,7 +564,9 @@ export function doSignOut() {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const user = selectUser(state);
|
const user = selectUser(state);
|
||||||
try {
|
try {
|
||||||
await pushDisconnect(user.id);
|
if (pushNotifications.supported) {
|
||||||
|
await pushNotifications.disconnect(user.id);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
Lbryio.call('user', 'signout')
|
Lbryio.call('user', 'signout')
|
||||||
.then(doSignOutCleanup)
|
.then(doSignOutCleanup)
|
||||||
|
|
3
ui/scss/component/_utils.scss
Normal file
3
ui/scss/component/_utils.scss
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.opacity-40 {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
|
@ -15,3 +15,9 @@
|
||||||
color: var(--color-text-subtitle);
|
color: var(--color-text-subtitle);
|
||||||
font-size: var(--font-small);
|
font-size: var(--font-small);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notificationsBlocked__subTextList {
|
||||||
|
li {
|
||||||
|
margin-left: var(--spacing-m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import Button from 'component/button';
|
||||||
import usePersistedState from 'effects/use-persisted-state';
|
import usePersistedState from 'effects/use-persisted-state';
|
||||||
|
|
||||||
export const BrowserNotificationBanner = () => {
|
export const BrowserNotificationBanner = () => {
|
||||||
const { pushSupported, pushEnabled, pushPermission, pushToggle } = useBrowserNotifications();
|
const { pushSupported, pushEnabled, pushPermission, pushToggle, pushErrorModal } = useBrowserNotifications();
|
||||||
const [hasAcknowledgedPush, setHasAcknowledgedPush] = usePersistedState('push-nag', false);
|
const [hasAcknowledgedPush, setHasAcknowledgedPush] = usePersistedState('push-nag', false);
|
||||||
|
|
||||||
if (!pushSupported || pushEnabled || pushPermission === 'denied' || hasAcknowledgedPush) return null;
|
if (!pushSupported || pushEnabled || pushPermission === 'denied' || hasAcknowledgedPush) return null;
|
||||||
|
@ -16,6 +16,7 @@ export const BrowserNotificationBanner = () => {
|
||||||
const handleClose = () => setHasAcknowledgedPush(true);
|
const handleClose = () => setHasAcknowledgedPush(true);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div className="browserNotificationsBanner notice-message">
|
<div className="browserNotificationsBanner notice-message">
|
||||||
<div className="browserNotificationsBanner__overview">
|
<div className="browserNotificationsBanner__overview">
|
||||||
<Icon className="browserNotificationsBanner__icon" icon={ICONS.NOTIFICATION} size={32} />
|
<Icon className="browserNotificationsBanner__icon" icon={ICONS.NOTIFICATION} size={32} />
|
||||||
|
@ -36,6 +37,8 @@ export const BrowserNotificationBanner = () => {
|
||||||
<Button button="close" title={__('Dismiss')} icon={ICONS.REMOVE} onClick={handleClose} />
|
<Button button="close" title={__('Dismiss')} icon={ICONS.REMOVE} onClick={handleClose} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{pushErrorModal()}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
61
web/component/browserNotificationHints/index.jsx
Normal file
61
web/component/browserNotificationHints/index.jsx
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as ICONS from 'constants/icons';
|
||||||
|
import Icon from 'component/common/icon';
|
||||||
|
import { Modal } from 'modal/modal';
|
||||||
|
import 'scss/component/notifications-blocked.scss';
|
||||||
|
|
||||||
|
type InlineMessageProps = {
|
||||||
|
title: string,
|
||||||
|
children: React.Node,
|
||||||
|
};
|
||||||
|
|
||||||
|
const InlineMessage = (props: InlineMessageProps) => {
|
||||||
|
const { title, children } = props;
|
||||||
|
return (
|
||||||
|
<div className="notificationsBlocked">
|
||||||
|
<Icon className="notificationsBlocked__icon" color="#E50054" icon={ICONS.ALERT} size={32} />
|
||||||
|
<div>
|
||||||
|
<span>{title}</span>
|
||||||
|
<span className={'notificationsBlocked__subText'}>{children}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BrowserNotificationsBlocked = () => {
|
||||||
|
return (
|
||||||
|
<InlineMessage title={__('Heads up: browser notifications are currently blocked in this browser.')}>
|
||||||
|
{__('To enable push notifications please configure your browser to allow notifications on odysee.com.')}
|
||||||
|
</InlineMessage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BrowserNotificationHints = () => {
|
||||||
|
return (
|
||||||
|
<InlineMessage title={__("Browser notifications aren't supported. Here's a few tips:")}>
|
||||||
|
<ul className={'notificationsBlocked__subText notificationsBlocked__subTextList'}>
|
||||||
|
<li>{__("Notifications aren't available when in incognito or private mode.")}</li>
|
||||||
|
<li>
|
||||||
|
{__(
|
||||||
|
"On Firefox, notifications won't function if cookies are set to clear on browser close. Please disable or add an exception for Odysee, then refresh."
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li>{__('For Brave, enable google push notifications in settings.')}</li>
|
||||||
|
<li>{__('Check browser settings to see if notifications are disabled or otherwise restricted.')}</li>
|
||||||
|
</ul>
|
||||||
|
</InlineMessage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type ModalProps = {
|
||||||
|
doHideModal: () => void,
|
||||||
|
};
|
||||||
|
export const BrowserNotificationErrorModal = (props: ModalProps) => {
|
||||||
|
const { doHideModal } = props;
|
||||||
|
return (
|
||||||
|
<Modal type="card" isOpen onAborted={doHideModal}>
|
||||||
|
<BrowserNotificationHints />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,39 +1,47 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { useEffect, useState, useMemo } from 'react';
|
import React, { useEffect, useState, useMemo } from 'react';
|
||||||
import { pushSubscribe, pushUnsubscribe, pushIsSubscribed } from '$web/src/push-notifications';
|
import pushNotifications from '$web/src/push-notifications';
|
||||||
import { isSupported } from 'firebase/messaging';
|
import { BrowserNotificationErrorModal } from '$web/component/browserNotificationHints';
|
||||||
|
|
||||||
// @todo: Once we are on Redux 7 we should have proper hooks we can use here for store access.
|
// @todo: Once we are on Redux 7 we should have proper hooks we can use here for store access.
|
||||||
import { store } from '$ui/store';
|
import { store } from '$ui/store';
|
||||||
import { selectUser } from 'redux/selectors/user';
|
import { selectUser } from 'redux/selectors/user';
|
||||||
import { doToast } from 'redux/actions/notifications';
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const [pushPermission, setPushPermission] = useState(window.Notification?.permission);
|
const [pushPermission, setPushPermission] = useState(window.Notification?.permission);
|
||||||
const [subscribed, setSubscribed] = useState(false);
|
const [subscribed, setSubscribed] = useState(false);
|
||||||
const [pushEnabled, setPushEnabled] = useState(false);
|
const [pushEnabled, setPushEnabled] = useState(false);
|
||||||
const [pushSupported, setPushSupported] = useState(false);
|
const [pushSupported, setPushSupported] = useState(false);
|
||||||
|
const [encounteredError, setEncounteredError] = useState(false);
|
||||||
|
|
||||||
const [user] = useState(selectUser(store.getState()));
|
const [user] = useState(selectUser(store.getState()));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
pushIsSubscribed(user.id).then((isSubscribed: boolean) => setSubscribed(isSubscribed));
|
setPushSupported(pushNotifications.supported);
|
||||||
isSupported().then((supported: boolean) => setPushSupported(supported));
|
if (pushNotifications.supported) {
|
||||||
|
pushNotifications.subscribed(user.id).then((isSubscribed: boolean) => setSubscribed(isSubscribed));
|
||||||
|
}
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
useMemo(() => setPushEnabled(pushPermission === 'granted' && subscribed), [pushPermission, subscribed]);
|
useMemo(() => setPushEnabled(pushPermission === 'granted' && subscribed), [pushPermission, subscribed]);
|
||||||
|
|
||||||
const subscribe = async () => {
|
const subscribe = async () => {
|
||||||
if (await pushSubscribe(user.id)) {
|
setEncounteredError(false);
|
||||||
|
try {
|
||||||
|
if (await pushNotifications.subscribe(user.id)) {
|
||||||
setSubscribed(true);
|
setSubscribed(true);
|
||||||
setPushPermission(window.Notification?.permission);
|
setPushPermission(window.Notification?.permission);
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
showError();
|
setEncounteredError(true);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setEncounteredError(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const unsubscribe = async () => {
|
const unsubscribe = async () => {
|
||||||
if (await pushUnsubscribe(user.id)) {
|
if (await pushNotifications.unsubscribe(user.id)) {
|
||||||
setSubscribed(false);
|
setSubscribed(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -46,15 +54,8 @@ export default () => {
|
||||||
return window.Notification?.permission !== 'granted' ? subscribe() : null;
|
return window.Notification?.permission !== 'granted' ? subscribe() : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const showError = () => {
|
const pushErrorModal = () => {
|
||||||
store.dispatch(
|
return <>{encounteredError && <BrowserNotificationErrorModal doHideModal={() => setEncounteredError(false)} />}</>;
|
||||||
doToast({
|
|
||||||
isError: true,
|
|
||||||
message: __(
|
|
||||||
'There was an error enabling browser notifications. Please make sure your browser settings allow you to subscribe to notifications.'
|
|
||||||
),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -63,5 +64,6 @@ export default () => {
|
||||||
pushPermission,
|
pushPermission,
|
||||||
pushToggle,
|
pushToggle,
|
||||||
pushRequest,
|
pushRequest,
|
||||||
|
pushErrorModal,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,39 +1,37 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import * as React from 'react';
|
||||||
import * as ICONS from 'constants/icons';
|
|
||||||
import SettingsRow from 'component/settingsRow';
|
import SettingsRow from 'component/settingsRow';
|
||||||
import { FormField } from 'component/common/form';
|
import { FormField } from 'component/common/form';
|
||||||
import useBrowserNotifications from '$web/component/browserNotificationSettings/use-browser-notifications';
|
import useBrowserNotifications from '$web/component/browserNotificationSettings/use-browser-notifications';
|
||||||
import 'scss/component/notifications-blocked.scss';
|
import { BrowserNotificationHints, BrowserNotificationsBlocked } from '$web/component/browserNotificationHints';
|
||||||
import Icon from 'component/common/icon';
|
|
||||||
|
|
||||||
const BrowserNotificationsBlocked = () => {
|
|
||||||
return (
|
|
||||||
<div className="notificationsBlocked">
|
|
||||||
<Icon className="notificationsBlocked__icon" color="#E50054" icon={ICONS.ALERT} size={32} />
|
|
||||||
<div>
|
|
||||||
<span>{__('Heads up: browser notifications are currently blocked in this browser.')}</span>
|
|
||||||
<span className={'notificationsBlocked__subText'}>
|
|
||||||
{__('To enable push notifications please configure your browser to allow notifications on odysee.com.')}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const BrowserNotificationSettings = () => {
|
const BrowserNotificationSettings = () => {
|
||||||
const { pushSupported, pushEnabled, pushPermission, pushToggle } = useBrowserNotifications();
|
const { pushSupported, pushEnabled, pushPermission, pushToggle, pushErrorModal } = useBrowserNotifications();
|
||||||
|
|
||||||
if (!pushSupported) return null;
|
const pushBlocked = pushPermission === 'denied';
|
||||||
if (pushPermission === 'denied') return <BrowserNotificationsBlocked />;
|
|
||||||
|
const renderHints = () => (!pushSupported ? <BrowserNotificationHints /> : null);
|
||||||
|
const renderBlocked = () => (pushBlocked ? <BrowserNotificationsBlocked /> : null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<SettingsRow
|
<SettingsRow
|
||||||
title={__('Browser Notifications')}
|
title={__('Browser Notifications')}
|
||||||
subtitle={__("Receive push notifications in this browser, even when you're not on odysee.com")}
|
subtitle={__("Receive push notifications in this browser, even when you're not on odysee.com")}
|
||||||
|
disabled={!pushSupported || pushBlocked}
|
||||||
>
|
>
|
||||||
<FormField type="checkbox" name="browserNotification" onChange={pushToggle} checked={pushEnabled} />
|
<FormField
|
||||||
|
type="checkbox"
|
||||||
|
name="browserNotification"
|
||||||
|
disabled={!pushSupported || pushBlocked}
|
||||||
|
onChange={pushToggle}
|
||||||
|
checked={pushEnabled}
|
||||||
|
/>
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
|
{renderHints()}
|
||||||
|
{renderBlocked()}
|
||||||
|
{pushErrorModal()}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -69,3 +69,4 @@
|
||||||
@import '../../ui/scss/component/empty';
|
@import '../../ui/scss/component/empty';
|
||||||
@import '../../ui/scss/component/stripe-card';
|
@import '../../ui/scss/component/stripe-card';
|
||||||
@import '../../ui/scss/component/wallet-tip-send';
|
@import '../../ui/scss/component/wallet-tip-send';
|
||||||
|
@import '../../ui/scss/component/utils';
|
||||||
|
|
|
@ -8,15 +8,45 @@ import { Lbryio } from 'lbryinc';
|
||||||
import { initializeApp } from 'firebase/app';
|
import { initializeApp } from 'firebase/app';
|
||||||
import { getMessaging, getToken, deleteToken } from 'firebase/messaging';
|
import { getMessaging, getToken, deleteToken } from 'firebase/messaging';
|
||||||
import { firebaseConfig, vapidKey } from '$web/src/firebase-config';
|
import { firebaseConfig, vapidKey } from '$web/src/firebase-config';
|
||||||
import { addRegistration, removeRegistration, hasRegistration } from '$web/src/fcm-management';
|
import { addRegistration, removeRegistration, hasRegistration } from '$web/src/push-notifications/fcm-management';
|
||||||
import { browserData } from '$web/src/ua';
|
import { browserData } from '$web/src/ua';
|
||||||
|
import { isPushSupported } from '$web/src/push-notifications/push-supported';
|
||||||
|
|
||||||
let messaging = null;
|
let messaging = null;
|
||||||
|
let pushSystem = null;
|
||||||
|
|
||||||
if ('serviceWorker' in navigator) {
|
(async () => {
|
||||||
|
const supported = await isPushSupported();
|
||||||
|
if (supported) {
|
||||||
const app = initializeApp(firebaseConfig);
|
const app = initializeApp(firebaseConfig);
|
||||||
messaging = getMessaging(app);
|
messaging = getMessaging(app);
|
||||||
}
|
pushSystem = {
|
||||||
|
supported: true,
|
||||||
|
subscribe,
|
||||||
|
unsubscribe,
|
||||||
|
subscribed,
|
||||||
|
reconnect,
|
||||||
|
disconnect,
|
||||||
|
validate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Proxy will forward to push system if it's supported.
|
||||||
|
// $FlowIssue[incompatible-type]
|
||||||
|
export default new Proxy(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get(target, prop) {
|
||||||
|
if (pushSystem) {
|
||||||
|
return pushSystem[prop];
|
||||||
|
} else {
|
||||||
|
if (prop === 'supported') return false;
|
||||||
|
throw new Error('Push notifications are not supported in this browser environment.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const subscriptionMetaData = () => {
|
const subscriptionMetaData = () => {
|
||||||
const isMobile = window.navigator.userAgentData?.mobile || false;
|
const isMobile = window.navigator.userAgentData?.mobile || false;
|
||||||
|
@ -31,7 +61,7 @@ const getFcmToken = async (): Promise<string | void> => {
|
||||||
return getToken(messaging, { serviceWorkerRegistration: swRegistration, vapidKey });
|
return getToken(messaging, { serviceWorkerRegistration: swRegistration, vapidKey });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pushSubscribe = async (userId: number, permanent: boolean = true): Promise<boolean> => {
|
const subscribe = async (userId: number, permanent: boolean = true): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const fcmToken = await getFcmToken();
|
const fcmToken = await getFcmToken();
|
||||||
if (!fcmToken) return false;
|
if (!fcmToken) return false;
|
||||||
|
@ -43,7 +73,7 @@ export const pushSubscribe = async (userId: number, permanent: boolean = true):
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pushUnsubscribe = async (userId: number, permanent: boolean = true): Promise<boolean> => {
|
const unsubscribe = async (userId: number, permanent: boolean = true): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const fcmToken = await getFcmToken();
|
const fcmToken = await getFcmToken();
|
||||||
if (!fcmToken) return false;
|
if (!fcmToken) return false;
|
||||||
|
@ -56,7 +86,7 @@ export const pushUnsubscribe = async (userId: number, permanent: boolean = true)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pushIsSubscribed = async (userId: number): Promise<boolean> => {
|
const subscribed = async (userId: number): Promise<boolean> => {
|
||||||
const swRegistration = await navigator.serviceWorker?.ready;
|
const swRegistration = await navigator.serviceWorker?.ready;
|
||||||
if (!swRegistration || !swRegistration.pushManager) return false;
|
if (!swRegistration || !swRegistration.pushManager) return false;
|
||||||
const browserSubscriptionExists = (await swRegistration.pushManager.getSubscription()) !== null;
|
const browserSubscriptionExists = (await swRegistration.pushManager.getSubscription()) !== null;
|
||||||
|
@ -64,17 +94,17 @@ export const pushIsSubscribed = async (userId: number): Promise<boolean> => {
|
||||||
return browserSubscriptionExists && userRecordExists;
|
return browserSubscriptionExists && userRecordExists;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pushReconnect = async (userId: number): Promise<boolean> => {
|
const reconnect = async (userId: number): Promise<boolean> => {
|
||||||
if (hasRegistration(userId)) return pushSubscribe(userId, false);
|
if (hasRegistration(userId)) return subscribe(userId, false);
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pushDisconnect = async (userId: number): Promise<boolean> => {
|
const disconnect = async (userId: number): Promise<boolean> => {
|
||||||
if (hasRegistration(userId)) return pushUnsubscribe(userId, false);
|
if (hasRegistration(userId)) return unsubscribe(userId, false);
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pushValidate = async (userId: number) => {
|
const validate = async (userId: number) => {
|
||||||
if (!hasRegistration(userId)) return;
|
if (!hasRegistration(userId)) return;
|
||||||
window.requestIdleCallback(async () => {
|
window.requestIdleCallback(async () => {
|
||||||
const serverTokens = await Lbryio.call('cfm', 'list');
|
const serverTokens = await Lbryio.call('cfm', 'list');
|
||||||
|
@ -82,7 +112,7 @@ export const pushValidate = async (userId: number) => {
|
||||||
if (!fcmToken) return;
|
if (!fcmToken) return;
|
||||||
const exists = serverTokens.find((item) => item.value === fcmToken);
|
const exists = serverTokens.find((item) => item.value === fcmToken);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
await pushSubscribe(userId, false);
|
await subscribe(userId, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
16
web/src/push-notifications/push-supported.js
Normal file
16
web/src/push-notifications/push-supported.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { isSupported } from 'firebase/messaging';
|
||||||
|
|
||||||
|
export const isPushSupported = async (): Promise<boolean> => {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
// Some browsers incognito expose sw but not the registration, while other don't expose sw at all.
|
||||||
|
// $FlowIssue[incompatible-type]
|
||||||
|
const activeRegistrations: Array<ServiceWorkerRegistration> = await navigator.serviceWorker.getRegistrations();
|
||||||
|
const swRegistered = activeRegistrations.length > 0;
|
||||||
|
const firebaseSupported = await isSupported();
|
||||||
|
const notificationFeature = 'Notification' in window;
|
||||||
|
return swRegistered && firebaseSupported && notificationFeature;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
Loading…
Reference in a new issue