UNPKG

@selfcommunity/react-core

Version:

React Core Components useful for integrating UI Community components (react-ui).

366 lines (365 loc) • 18 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SUPPORTED_BROWSER_TYPES = exports.DEFAULT_BROWSER_TYPE = exports.EDGE_BROWSER_TYPE = exports.OPERA_BROWSER_TYPE = exports.FIREFOX_BROWSER_TYPE = exports.CHROME_BROWSER_TYPE = void 0; const tslib_1 = require("tslib"); const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = require("react"); const SCContextProvider_1 = require("../components/provider/SCContextProvider"); const SCUserProvider_1 = require("../components/provider/SCUserProvider"); const utils_1 = require("@selfcommunity/utils"); const Button_1 = tslib_1.__importDefault(require("@mui/material/Button")); const utils_2 = require("@selfcommunity/utils"); const Errors_1 = require("../constants/Errors"); const api_services_1 = require("@selfcommunity/api-services"); const Device_1 = require("../constants/Device"); const SCPreferencesProvider_1 = require("../components/provider/SCPreferencesProvider"); const notistack_1 = require("notistack"); const SCPreferences = tslib_1.__importStar(require("../constants/Preferences")); const Notifications_1 = require("../constants/Notifications"); const js_cookie_1 = tslib_1.__importDefault(require("js-cookie")); const react_intl_1 = require("react-intl"); const notification_1 = require("../utils/notification"); /** * Browser supported on backend */ exports.CHROME_BROWSER_TYPE = 'chrome'; exports.FIREFOX_BROWSER_TYPE = 'firefox'; exports.OPERA_BROWSER_TYPE = 'opera'; exports.EDGE_BROWSER_TYPE = 'edge'; exports.DEFAULT_BROWSER_TYPE = exports.CHROME_BROWSER_TYPE; exports.SUPPORTED_BROWSER_TYPES = [exports.CHROME_BROWSER_TYPE, exports.FIREFOX_BROWSER_TYPE, exports.OPERA_BROWSER_TYPE, exports.EDGE_BROWSER_TYPE]; /** :::info This custom hook is used to init web push messaging. ::: */ function useSCWebPushMessaging() { // CONTEXT const scContext = (0, SCContextProvider_1.useSCContext)(); const scUserContext = (0, SCUserProvider_1.useSCUser)(); const scPreferencesContext = (0, react_1.useContext)(SCPreferencesProvider_1.SCPreferencesContext); const intl = (0, react_intl_1.useIntl)(); const { enqueueSnackbar, closeSnackbar } = (0, notistack_1.useSnackbar)(); // STATE const [wpSubscription, setWpSubscription] = (0, react_1.useState)(null); // REFS const subAttempts = (0, react_1.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 (!js_cookie_1.default.get(Notifications_1.NOTIFICATIONS_WEB_PUSH_MESSAGING_DIALOG_COOKIE)) { enqueueSnackbar(intl.formatMessage({ id: 'ui.webPushNotification.requestPermission', defaultMessage: 'ui.webPushNotification.requestPermission' }), { action: (snackbarId) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(Button_1.default, Object.assign({ size: "small", sx: { color: '#FFF' }, onClick: () => requestNotificationPermission(snackbarId) }, { children: intl.formatMessage({ id: 'ui.webPushNotification.allow', defaultMessage: 'ui.webPushNotification.allow' }) })), (0, jsx_runtime_1.jsx)(Button_1.default, 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 { utils_1.Logger.info(Errors_1.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} js_cookie_1.default.set(Notifications_1.NOTIFICATIONS_WEB_PUSH_MESSAGING_DIALOG_COOKIE, '1', { expires: 1 }); } }; /** * perform update the Subscription * @param data * @param remove */ const performUpdateSubscription = (data, remove) => { const url = remove ? api_services_1.Endpoints.DeleteDevice.url({ type: Device_1.WEB_PUSH_NOTIFICATION_DEVICE_TYPE, id: data.registration_id }) : api_services_1.Endpoints.NewDevice.url({ type: Device_1.WEB_PUSH_NOTIFICATION_DEVICE_TYPE }); const method = remove ? api_services_1.Endpoints.DeleteDevice.method : api_services_1.Endpoints.NewDevice.method; return api_services_1.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 (exports.SUPPORTED_BROWSER_TYPES.indexOf(browser.name) < 0) { return exports.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 (exports.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 = (0, utils_2.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 { utils_1.Logger.info(Errors_1.SCOPE_SC_CORE, 'Web Push Notifications subscription created successfully'); setWpSubscription(res); } }) .catch((e) => { utils_1.Logger.error(Errors_1.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(() => { utils_1.Logger.info(Errors_1.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 utils_1.Logger.info(Errors_1.SCOPE_SC_CORE, `Error on removing web push notification subscription.`); console.log(e); }); }) .catch(function (e) { utils_1.Logger.info(Errors_1.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: (0, utils_2.urlB64ToUint8Array)(applicationServerKey) }) .then(function (subscription) { if (!subscription) { utils_1.Logger.info(Errors_1.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 utils_1.Logger.info(Errors_1.SCOPE_SC_CORE, 'Permission for Web Push Notifications was denied'); } else { // A problem occurred with the subscription subAttempts.current += 1; utils_1.Logger.info(Errors_1.SCOPE_SC_CORE, `Unable to subscribe(${subAttempts.current}) to push. ${e}`); if (subAttempts.current < 3) { unsubscribe(() => subscribe()); } else { utils_1.Logger.info(Errors_1.SCOPE_SC_CORE, `Unable to subscribe to Web Push Notification. ${e}`); } } }); } else { utils_1.Logger.info(Errors_1.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) => { utils_1.Logger.info(Errors_1.SCOPE_SC_CORE, 'Error during getSubscription()'); console.log(err); }); }); } else { utils_1.Logger.info(Errors_1.SCOPE_SC_CORE, 'Permission for Notifications was denied'); } }) .catch((err) => { console.log(err); utils_1.Logger.info(Errors_1.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') { utils_1.Logger.info(Errors_1.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)) { utils_1.Logger.info(Errors_1.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') { utils_1.Logger.info(Errors_1.SCOPE_SC_CORE, 'The user has blocked Web Push Notifications.'); return; } else { if (Notification.permission === 'default') { utils_1.Logger.info(Errors_1.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 */ (0, react_1.useEffect)(() => { if (!wpSubscription && scUserContext.user) { if (!(0, notification_1.isWebPushMessagingEnabled)()) { utils_1.Logger.warn(Errors_1.SCOPE_SC_CORE, 'Mobile native notifications replace web push messages with this settings.'); } else if (!webPushPreferenceEnabled || scContext.settings.notifications.webPushMessaging.disableToastMessage) { utils_1.Logger.warn(Errors_1.SCOPE_SC_CORE, 'This instance is not configured to support web push notifications or they have been disabled.'); } else if (!applicationServerKey) { utils_1.Logger.warn(Errors_1.SCOPE_SC_CORE, 'Invalid or missing applicationServerKey. Check the configuration that is passed to the SCContextProvider.'); } else { utils_1.Logger.info(Errors_1.SCOPE_SC_CORE, 'Initialize web push notification.'); initialiseState(); } } if ((!scUserContext.user || !(0, notification_1.isWebPushMessagingEnabled)()) && wpSubscription) { // Unsubscribe if user not in session unsubscribe(() => { if (navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('Chrome')) { js_cookie_1.default.remove(Notifications_1.NOTIFICATIONS_WEB_PUSH_MESSAGING_DIALOG_COOKIE); } }); } }, [scUserContext.user, scContext.settings.notifications.webPushMessaging, wpSubscription]); return { wpSubscription, requestNotificationPermission }; } exports.default = useSCWebPushMessaging;