@selfcommunity/react-core
Version:
React Core Components useful for integrating UI Community components (react-ui).
361 lines (360 loc) • 16.8 kB
JavaScript
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { useContext, useEffect, useRef, useState } from 'react';
import { useSCContext } from '../components/provider/SCContextProvider';
import { useSCUser } from '../components/provider/SCUserProvider';
import { Logger } from '@selfcommunity/utils';
import Button from '@mui/material/Button';
import { loadVersionBrowser, urlB64ToUint8Array } from '@selfcommunity/utils';
import { SCOPE_SC_CORE } from '../constants/Errors';
import { http, Endpoints } from '@selfcommunity/api-services';
import { WEB_PUSH_NOTIFICATION_DEVICE_TYPE } from '../constants/Device';
import { SCPreferencesContext } from '../components/provider/SCPreferencesProvider';
import { useSnackbar } from 'notistack';
import * as SCPreferences from '../constants/Preferences';
import { NOTIFICATIONS_WEB_PUSH_MESSAGING_DIALOG_COOKIE } from '../constants/Notifications';
import Cookies from 'js-cookie';
import { useIntl } from 'react-intl';
import { isWebPushMessagingEnabled } from '../utils/notification';
/**
* Browser supported on backend
*/
export const CHROME_BROWSER_TYPE = 'chrome';
export const FIREFOX_BROWSER_TYPE = 'firefox';
export const OPERA_BROWSER_TYPE = 'opera';
export const EDGE_BROWSER_TYPE = 'edge';
export const DEFAULT_BROWSER_TYPE = CHROME_BROWSER_TYPE;
export const SUPPORTED_BROWSER_TYPES = [CHROME_BROWSER_TYPE, FIREFOX_BROWSER_TYPE, OPERA_BROWSER_TYPE, EDGE_BROWSER_TYPE];
/**
:::info
This custom hook is used to init web push messaging.
:::
*/
export default function useSCWebPushMessaging() {
// CONTEXT
const scContext = useSCContext();
const scUserContext = useSCUser();
const scPreferencesContext = useContext(SCPreferencesContext);
const intl = useIntl();
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
// STATE
const [wpSubscription, setWpSubscription] = useState(null);
// REFS
const subAttempts = useRef(0);
// CONST
const applicationServerKey = scContext.settings.notifications.webPushMessaging.applicationServerKey !== undefined
? scContext.settings.notifications.webPushMessaging.applicationServerKey
: SCPreferences.PROVIDERS_WEB_PUSH_PUBLIC_KEY in scPreferencesContext.preferences
? scPreferencesContext.preferences[SCPreferences.PROVIDERS_WEB_PUSH_PUBLIC_KEY].value
: null;
const webPushPreferenceEnabled = SCPreferences.PROVIDERS_WEB_PUSH_ENABLED in scPreferencesContext.preferences &&
scPreferencesContext.preferences[SCPreferences.PROVIDERS_WEB_PUSH_ENABLED].value
? scPreferencesContext.preferences[SCPreferences.PROVIDERS_WEB_PUSH_ENABLED].value
: false;
/**
* Show custom Snackbar dialog to request permission
* User action required for some browser (ex. Safari)
*/
const showCustomRequestNotificationSnackbar = () => {
if (!Cookies.get(NOTIFICATIONS_WEB_PUSH_MESSAGING_DIALOG_COOKIE)) {
enqueueSnackbar(intl.formatMessage({ id: 'ui.webPushNotification.requestPermission', defaultMessage: 'ui.webPushNotification.requestPermission' }), {
action: (snackbarId) => (_jsxs(_Fragment, { children: [_jsx(Button, Object.assign({ size: "small", sx: { color: '#FFF' }, onClick: () => requestNotificationPermission(snackbarId) }, { children: intl.formatMessage({ id: 'ui.webPushNotification.allow', defaultMessage: 'ui.webPushNotification.allow' }) })), _jsx(Button, Object.assign({ size: "small", sx: { color: '#FFF' }, onClick: () => closeRequestNotificationSnackbar(snackbarId) }, { children: intl.formatMessage({ id: 'ui.webPushNotification.block', defaultMessage: 'ui.webPushNotification.block' }) }))] })),
variant: 'default',
anchorOrigin: { horizontal: 'center', vertical: 'bottom' },
preventDuplicate: true,
});
}
else {
Logger.info(SCOPE_SC_CORE, 'Skip show the dialog to request Web Push Notifications grant permission');
}
};
/**
* Close the SnackBar dialog if the request notification permission
* came from the custom dialog
* @param snackbarId
*/
const closeRequestNotificationSnackbar = (snackbarId) => {
if (snackbarId) {
closeSnackbar(snackbarId);
// To set expiration in minutes:
// let inFifteenMinutes = new Date(new Date().getTime() + 15 * 60 * 1000);
// {expires: inFifteenMinutes}
Cookies.set(NOTIFICATIONS_WEB_PUSH_MESSAGING_DIALOG_COOKIE, '1', { expires: 1 });
}
};
/**
* perform update the Subscription
* @param data
* @param remove
*/
const performUpdateSubscription = (data, remove) => {
const url = remove
? Endpoints.DeleteDevice.url({ type: WEB_PUSH_NOTIFICATION_DEVICE_TYPE, id: data.registration_id })
: Endpoints.NewDevice.url({ type: WEB_PUSH_NOTIFICATION_DEVICE_TYPE });
const method = remove ? Endpoints.DeleteDevice.method : Endpoints.NewDevice.method;
return http
.request(Object.assign({ url,
method }, (remove ? {} : { data: data })))
.then((res) => {
if (res.status >= 300) {
return Promise.reject(res);
}
return Promise.resolve(res.data);
});
};
/**
* Return browser type
* Fallback to DEFAULT_BROWSER_TYPE if the browser detected not in SUPPORTED_BROWSER_TYPES
*/
const getBrowserType = (browser) => {
if (SUPPORTED_BROWSER_TYPES.indexOf(browser.name) < 0) {
return DEFAULT_BROWSER_TYPE;
}
return browser.name;
};
/**
* Return registrationId based on browser type and subscription
* Fallback to subscription.endpoint if the browser detected not in SUPPORTED_BROWSER_TYPES
*/
const getRegistrationId = (browser, sub) => {
if (SUPPORTED_BROWSER_TYPES.indexOf(browser.name) < 0) {
return sub.endpoint;
}
try {
const endpointParts = sub.endpoint.split('/');
return endpointParts[endpointParts.length - 1];
}
catch (e) {
return sub.endpoint;
}
};
/**
* updateSubscriptionOnServer to sync current subscription
* @param sub
* @param remove
*/
const updateSubscriptionOnServer = (sub, remove) => {
const browser = loadVersionBrowser();
const data = {
browser: getBrowserType(browser).toUpperCase(),
p256dh: btoa(String.fromCharCode.apply(null, new Uint8Array(sub.getKey('p256dh')))),
auth: btoa(String.fromCharCode.apply(null, new Uint8Array(sub.getKey('auth')))),
user: scUserContext.user ? scUserContext.user.username : '',
registration_id: getRegistrationId(browser, sub),
};
return performUpdateSubscription(data, remove)
.then((res) => {
if (remove) {
setWpSubscription(null);
}
else {
Logger.info(SCOPE_SC_CORE, 'Web Push Notifications subscription created successfully');
setWpSubscription(res);
}
})
.catch((e) => {
Logger.error(SCOPE_SC_CORE, e);
});
};
/**
* Unsubscribe
* @param callback
*/
const unsubscribe = (callback = null) => {
navigator.serviceWorker.ready.then(function (serviceWorkerRegistration) {
if (serviceWorkerRegistration) {
// To unsubscribe from push messaging, you need get the
// subscription object, which you can call unsubscribe() on.
serviceWorkerRegistration.pushManager
.getSubscription()
.then((pushSubscription) => {
// Check we have a subscription to unsubscribe
if (!pushSubscription) {
// No subscription object, so set the state
// to allow the user to subscribe to push
return;
}
// We have a subscription, so call unsubscribe on it
pushSubscription
.unsubscribe()
.then(() => {
Logger.info(SCOPE_SC_CORE, 'Unsubscription successfully');
// Request to server to remove
// the users data from your data store so you
// don't attempt to send them push messages anymore
void updateSubscriptionOnServer(pushSubscription, true).then(() => {
callback && callback();
});
})
.catch(function (e) {
// We failed to unsubscribe, this can lead to
// an unusual state, so may be best to remove
// the subscription id from your data store and
// inform the user that you disabled push
Logger.info(SCOPE_SC_CORE, `Error on removing web push notification subscription.`);
console.log(e);
});
})
.catch(function (e) {
Logger.info(SCOPE_SC_CORE, `Error thrown while unsubscribing from push messaging. ${e}`);
});
}
});
};
/**
* Get subscription
*/
const subscribe = () => {
navigator.serviceWorker.getRegistration().then(function (serviceWorkerRegistration) {
if (serviceWorkerRegistration) {
// service worker is registered
serviceWorkerRegistration.pushManager
.subscribe({ userVisibleOnly: true, applicationServerKey: urlB64ToUint8Array(applicationServerKey) })
.then(function (subscription) {
if (!subscription) {
Logger.info(SCOPE_SC_CORE, 'We aren’t subscribed to push.');
}
else {
void updateSubscriptionOnServer(subscription, false);
}
})
.catch(function (e) {
console.log(e);
if (Notification.permission === 'denied') {
// The user denied the notification permission which
// means we failed to subscribe and the user will need
// to manually change the notification permission to
// subscribe to push messages
Logger.info(SCOPE_SC_CORE, 'Permission for Web Push Notifications was denied');
}
else {
// A problem occurred with the subscription
subAttempts.current += 1;
Logger.info(SCOPE_SC_CORE, `Unable to subscribe(${subAttempts.current}) to push. ${e}`);
if (subAttempts.current < 3) {
unsubscribe(() => subscribe());
}
else {
Logger.info(SCOPE_SC_CORE, `Unable to subscribe to Web Push Notification. ${e}`);
}
}
});
}
else {
Logger.info(SCOPE_SC_CORE, `To receive Web Push Notifications the service worker must be registered.`);
}
});
};
/**
* Request web push notification permission
* @type {(function(): void)|*}
*/
const requestNotificationPermission = (snackbarId) => {
if (Notification.permission !== 'granted') {
Notification.requestPermission()
.then(function (permission) {
if (permission === 'granted') {
navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
// Do we already have a push message subscription?
serviceWorkerRegistration.pushManager
.getSubscription()
.then(function (subscription) {
// Enable any UI which subscribes / unsubscribes from
// push messages.
if (!subscription) {
subscribe();
return;
}
// Keep your server in sync with the latest subscription
void updateSubscriptionOnServer(subscription, false);
})
.catch((err) => {
Logger.info(SCOPE_SC_CORE, 'Error during getSubscription()');
console.log(err);
});
});
}
else {
Logger.info(SCOPE_SC_CORE, 'Permission for Notifications was denied');
}
})
.catch((err) => {
console.log(err);
Logger.info(SCOPE_SC_CORE, 'Request web push permission by a user generated gesture');
});
}
closeRequestNotificationSnackbar(snackbarId);
};
/**
* Initialize state
*/
const initialiseState = () => {
/**
* Check if Notifications supported in the service worker
*/
if (typeof ServiceWorkerRegistration === 'undefined') {
Logger.info(SCOPE_SC_CORE, "Notifications aren't supported.");
return;
}
/**
* Features, such as service workers and push notifications, require HTTPS secure domains,
* so your development environment must serve content over HTTPS to match a production environment.
* Check if push messaging is supported.
*/
if (!('PushManager' in window && 'serviceWorker' in navigator && 'Notification' in window)) {
Logger.info(SCOPE_SC_CORE, "Service Worker or Push messaging aren't supported.");
return;
}
/**
* Check the current Notification permission.
* If it's denied, it's a permanent block until the user changes the permission
*/
if (Notification.permission === 'denied') {
Logger.info(SCOPE_SC_CORE, 'The user has blocked Web Push Notifications.');
return;
}
else {
if (Notification.permission === 'default') {
Logger.info(SCOPE_SC_CORE, 'Request permission');
if (navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('Chrome')) {
showCustomRequestNotificationSnackbar();
}
else {
requestNotificationPermission(null);
}
}
else if (Notification.permission === 'granted') {
subscribe();
}
}
};
/**
* Init state web push subscription
* If web push enabled, applicationServerKey and user is logged, check subscription
*/
useEffect(() => {
if (!wpSubscription && scUserContext.user) {
if (!isWebPushMessagingEnabled()) {
Logger.warn(SCOPE_SC_CORE, 'Mobile native notifications replace web push messages with this settings.');
}
else if (!webPushPreferenceEnabled || scContext.settings.notifications.webPushMessaging.disableToastMessage) {
Logger.warn(SCOPE_SC_CORE, 'This instance is not configured to support web push notifications or they have been disabled.');
}
else if (!applicationServerKey) {
Logger.warn(SCOPE_SC_CORE, 'Invalid or missing applicationServerKey. Check the configuration that is passed to the SCContextProvider.');
}
else {
Logger.info(SCOPE_SC_CORE, 'Initialize web push notification.');
initialiseState();
}
}
if ((!scUserContext.user || !isWebPushMessagingEnabled()) && wpSubscription) {
// Unsubscribe if user not in session
unsubscribe(() => {
if (navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('Chrome')) {
Cookies.remove(NOTIFICATIONS_WEB_PUSH_MESSAGING_DIALOG_COOKIE);
}
});
}
}, [scUserContext.user, scContext.settings.notifications.webPushMessaging, wpSubscription]);
return { wpSubscription, requestNotificationPermission };
}